summaryrefslogtreecommitdiffstats
path: root/src/com/android/messaging/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/messaging/ui')
-rw-r--r--src/com/android/messaging/ui/AsyncImageView.java457
-rw-r--r--src/com/android/messaging/ui/AttachmentPreview.java331
-rw-r--r--src/com/android/messaging/ui/AttachmentPreviewFactory.java299
-rw-r--r--src/com/android/messaging/ui/AudioAttachmentPlayPauseButton.java59
-rw-r--r--src/com/android/messaging/ui/AudioAttachmentView.java329
-rw-r--r--src/com/android/messaging/ui/AudioPlaybackProgressBar.java121
-rw-r--r--src/com/android/messaging/ui/BaseBugleActivity.java51
-rw-r--r--src/com/android/messaging/ui/BaseBugleFragmentActivity.java43
-rw-r--r--src/com/android/messaging/ui/BasePagerViewHolder.java106
-rw-r--r--src/com/android/messaging/ui/BlockedParticipantListItemView.java64
-rw-r--r--src/com/android/messaging/ui/BlockedParticipantsActivity.java57
-rw-r--r--src/com/android/messaging/ui/BlockedParticipantsFragment.java102
-rw-r--r--src/com/android/messaging/ui/BugleActionBarActivity.java356
-rw-r--r--src/com/android/messaging/ui/BugleAnimationTags.java38
-rw-r--r--src/com/android/messaging/ui/ClassZeroActivity.java205
-rw-r--r--src/com/android/messaging/ui/CompositeAdapter.java288
-rw-r--r--src/com/android/messaging/ui/ContactIconView.java152
-rw-r--r--src/com/android/messaging/ui/ConversationDrawables.java177
-rw-r--r--src/com/android/messaging/ui/CursorRecyclerAdapter.java333
-rw-r--r--src/com/android/messaging/ui/CustomHeaderPagerListViewHolder.java136
-rw-r--r--src/com/android/messaging/ui/CustomHeaderPagerViewHolder.java26
-rw-r--r--src/com/android/messaging/ui/CustomHeaderViewPager.java91
-rw-r--r--src/com/android/messaging/ui/CustomHeaderViewPagerAdapter.java33
-rw-r--r--src/com/android/messaging/ui/FixedViewPagerAdapter.java132
-rw-r--r--src/com/android/messaging/ui/ImeDetectFrameLayout.java46
-rw-r--r--src/com/android/messaging/ui/LicenseActivity.java35
-rw-r--r--src/com/android/messaging/ui/LineWrapLayout.java232
-rw-r--r--src/com/android/messaging/ui/ListEmptyView.java72
-rw-r--r--src/com/android/messaging/ui/MaxHeightScrollView.java50
-rw-r--r--src/com/android/messaging/ui/MultiAttachmentLayout.java424
-rw-r--r--src/com/android/messaging/ui/OrientedBitmapDrawable.java104
-rw-r--r--src/com/android/messaging/ui/PagerViewHolder.java34
-rw-r--r--src/com/android/messaging/ui/PagingAwareViewPager.java95
-rw-r--r--src/com/android/messaging/ui/PermissionCheckActivity.java141
-rw-r--r--src/com/android/messaging/ui/PersistentInstanceState.java39
-rw-r--r--src/com/android/messaging/ui/PersonItemView.java242
-rw-r--r--src/com/android/messaging/ui/PlaceholderInsetDrawable.java72
-rw-r--r--src/com/android/messaging/ui/PlainTextEditText.java85
-rw-r--r--src/com/android/messaging/ui/PlaybackStateView.java43
-rw-r--r--src/com/android/messaging/ui/RemoteInputEntrypointActivity.java58
-rw-r--r--src/com/android/messaging/ui/SmsStorageLowWarningActivity.java36
-rw-r--r--src/com/android/messaging/ui/SmsStorageLowWarningFragment.java267
-rw-r--r--src/com/android/messaging/ui/SnackBar.java314
-rw-r--r--src/com/android/messaging/ui/SnackBarInteraction.java67
-rw-r--r--src/com/android/messaging/ui/SnackBarManager.java365
-rw-r--r--src/com/android/messaging/ui/TestActivity.java88
-rw-r--r--src/com/android/messaging/ui/UIIntents.java378
-rw-r--r--src/com/android/messaging/ui/UIIntentsImpl.java577
-rw-r--r--src/com/android/messaging/ui/VCardDetailActivity.java60
-rw-r--r--src/com/android/messaging/ui/VCardDetailAdapter.java120
-rw-r--r--src/com/android/messaging/ui/VCardDetailFragment.java197
-rw-r--r--src/com/android/messaging/ui/VideoThumbnailView.java343
-rw-r--r--src/com/android/messaging/ui/ViewPagerTabStrip.java102
-rw-r--r--src/com/android/messaging/ui/ViewPagerTabs.java236
-rw-r--r--src/com/android/messaging/ui/WidgetPickConversationActivity.java114
-rw-r--r--src/com/android/messaging/ui/animation/PopupTransitionAnimation.java302
-rw-r--r--src/com/android/messaging/ui/animation/RectEvaluatorCompat.java45
-rw-r--r--src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java208
-rw-r--r--src/com/android/messaging/ui/appsettings/ApnEditorActivity.java463
-rw-r--r--src/com/android/messaging/ui/appsettings/ApnPreference.java151
-rw-r--r--src/com/android/messaging/ui/appsettings/ApnSettingsActivity.java406
-rw-r--r--src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java262
-rw-r--r--src/com/android/messaging/ui/appsettings/GroupMmsSettingDialog.java92
-rw-r--r--src/com/android/messaging/ui/appsettings/PerSubscriptionSettingsActivity.java246
-rw-r--r--src/com/android/messaging/ui/appsettings/PhoneNumberPreference.java116
-rw-r--r--src/com/android/messaging/ui/appsettings/SettingsActivity.java178
-rw-r--r--src/com/android/messaging/ui/attachmentchooser/AttachmentChooserActivity.java56
-rw-r--r--src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragment.java183
-rw-r--r--src/com/android/messaging/ui/attachmentchooser/AttachmentGridItemView.java119
-rw-r--r--src/com/android/messaging/ui/attachmentchooser/AttachmentGridView.java172
-rw-r--r--src/com/android/messaging/ui/contact/AddContactsConfirmationDialog.java85
-rw-r--r--src/com/android/messaging/ui/contact/AllContactsListViewHolder.java62
-rw-r--r--src/com/android/messaging/ui/contact/ContactDropdownLayouter.java138
-rw-r--r--src/com/android/messaging/ui/contact/ContactListAdapter.java86
-rw-r--r--src/com/android/messaging/ui/contact/ContactListItemView.java177
-rw-r--r--src/com/android/messaging/ui/contact/ContactPickerFragment.java607
-rw-r--r--src/com/android/messaging/ui/contact/ContactRecipientAdapter.java286
-rw-r--r--src/com/android/messaging/ui/contact/ContactRecipientAutoCompleteView.java289
-rw-r--r--src/com/android/messaging/ui/contact/ContactRecipientPhotoManager.java96
-rw-r--r--src/com/android/messaging/ui/contact/ContactSectionIndexer.java169
-rw-r--r--src/com/android/messaging/ui/contact/FrequentContactsListViewHolder.java63
-rw-r--r--src/com/android/messaging/ui/conversation/ComposeMessageView.java962
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationActivity.java379
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationActivityUiState.java306
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationFastScroller.java489
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationFragment.java1662
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationInput.java103
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationInputManager.java550
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java117
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java132
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationMessageView.java1206
-rw-r--r--src/com/android/messaging/ui/conversation/ConversationSimSelector.java128
-rw-r--r--src/com/android/messaging/ui/conversation/EnterSelfPhoneNumberDialog.java92
-rw-r--r--src/com/android/messaging/ui/conversation/LaunchConversationActivity.java134
-rw-r--r--src/com/android/messaging/ui/conversation/MessageBubbleBackground.java47
-rw-r--r--src/com/android/messaging/ui/conversation/MessageDetailsDialog.java381
-rw-r--r--src/com/android/messaging/ui/conversation/SimIconView.java51
-rw-r--r--src/com/android/messaging/ui/conversation/SimSelectorItemView.java90
-rw-r--r--src/com/android/messaging/ui/conversation/SimSelectorView.java169
-rw-r--r--src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java339
-rw-r--r--src/com/android/messaging/ui/conversationlist/ArchivedConversationListActivity.java96
-rw-r--r--src/com/android/messaging/ui/conversationlist/ConversationListActivity.java144
-rw-r--r--src/com/android/messaging/ui/conversationlist/ConversationListAdapter.java77
-rw-r--r--src/com/android/messaging/ui/conversationlist/ConversationListFragment.java446
-rw-r--r--src/com/android/messaging/ui/conversationlist/ConversationListItemView.java643
-rw-r--r--src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java462
-rw-r--r--src/com/android/messaging/ui/conversationlist/ForwardMessageActivity.java81
-rw-r--r--src/com/android/messaging/ui/conversationlist/MultiSelectActionModeCallback.java219
-rw-r--r--src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java177
-rw-r--r--src/com/android/messaging/ui/conversationlist/ShareIntentAdapter.java138
-rw-r--r--src/com/android/messaging/ui/conversationlist/ShareIntentFragment.java163
-rw-r--r--src/com/android/messaging/ui/conversationsettings/CopyContactDetailDialog.java66
-rw-r--r--src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsActivity.java66
-rw-r--r--src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsFragment.java329
-rw-r--r--src/com/android/messaging/ui/conversationsettings/PeopleOptionsItemView.java99
-rw-r--r--src/com/android/messaging/ui/debug/DebugMmsConfigActivity.java34
-rw-r--r--src/com/android/messaging/ui/debug/DebugMmsConfigFragment.java147
-rw-r--r--src/com/android/messaging/ui/debug/DebugMmsConfigItemView.java134
-rw-r--r--src/com/android/messaging/ui/debug/DebugSmsMmsFromDumpFileDialogFragment.java171
-rw-r--r--src/com/android/messaging/ui/mediapicker/AudioLevelSource.java73
-rw-r--r--src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java130
-rw-r--r--src/com/android/messaging/ui/mediapicker/AudioRecordView.java351
-rw-r--r--src/com/android/messaging/ui/mediapicker/CameraManager.java1200
-rw-r--r--src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java481
-rw-r--r--src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java102
-rw-r--r--src/com/android/messaging/ui/mediapicker/CameraPreview.java152
-rw-r--r--src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java128
-rw-r--r--src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java62
-rw-r--r--src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java159
-rw-r--r--src/com/android/messaging/ui/mediapicker/GalleryGridView.java315
-rw-r--r--src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java230
-rw-r--r--src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java118
-rw-r--r--src/com/android/messaging/ui/mediapicker/ImagePersistTask.java172
-rw-r--r--src/com/android/messaging/ui/mediapicker/LevelTrackingMediaRecorder.java223
-rw-r--r--src/com/android/messaging/ui/mediapicker/MediaChooser.java216
-rw-r--r--src/com/android/messaging/ui/mediapicker/MediaPicker.java736
-rw-r--r--src/com/android/messaging/ui/mediapicker/MediaPickerGridView.java44
-rw-r--r--src/com/android/messaging/ui/mediapicker/MediaPickerPanel.java563
-rw-r--r--src/com/android/messaging/ui/mediapicker/MmsVideoRecorder.java127
-rw-r--r--src/com/android/messaging/ui/mediapicker/PausableChronometer.java75
-rw-r--r--src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java114
-rw-r--r--src/com/android/messaging/ui/mediapicker/SoundLevels.java212
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java24
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java589
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java95
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java202
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java825
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/README.txt3
-rw-r--r--src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java178
-rw-r--r--src/com/android/messaging/ui/photoviewer/BuglePhotoBitmapLoader.java192
-rw-r--r--src/com/android/messaging/ui/photoviewer/BuglePhotoPageAdapter.java38
-rw-r--r--src/com/android/messaging/ui/photoviewer/BuglePhotoViewActivity.java31
-rw-r--r--src/com/android/messaging/ui/photoviewer/BuglePhotoViewController.java179
-rw-r--r--src/com/android/messaging/ui/photoviewer/BuglePhotoViewFragment.java89
154 files changed, 0 insertions, 33536 deletions
diff --git a/src/com/android/messaging/ui/AsyncImageView.java b/src/com/android/messaging/ui/AsyncImageView.java
deleted file mode 100644
index 9aaf0b1..0000000
--- a/src/com/android/messaging/ui/AsyncImageView.java
+++ /dev/null
@@ -1,457 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Path;
-import android.graphics.RectF;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.support.annotation.Nullable;
-import android.support.rastermill.FrameSequenceDrawable;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.widget.ImageView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.media.BindableMediaRequest;
-import com.android.messaging.datamodel.media.GifImageResource;
-import com.android.messaging.datamodel.media.ImageRequest;
-import com.android.messaging.datamodel.media.ImageRequestDescriptor;
-import com.android.messaging.datamodel.media.ImageResource;
-import com.android.messaging.datamodel.media.MediaRequest;
-import com.android.messaging.datamodel.media.MediaResourceManager;
-import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.ThreadUtil;
-import com.android.messaging.util.UiUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.HashSet;
-
-/**
- * An ImageView used to asynchronously request an image from MediaResourceManager and render it.
- */
-public class AsyncImageView extends ImageView implements MediaResourceLoadListener<ImageResource> {
- private static final String TAG = LogUtil.BUGLE_DATAMODEL_TAG;
- // 100ms delay before disposing the image in case the AsyncImageView is re-added to the UI
- private static final int DISPOSE_IMAGE_DELAY = 100;
-
- // AsyncImageView has a 1-1 binding relationship with an ImageRequest instance that requests
- // the image from the MediaResourceManager. Since the request is done asynchronously, we
- // want to make sure the image view is always bound to the latest image request that it
- // issues, so that when the image is loaded, the ImageRequest (which extends BindableData)
- // will be able to figure out whether the binding is still valid and whether the loaded image
- // should be delivered to the AsyncImageView via onMediaResourceLoaded() callback.
- @VisibleForTesting
- public final Binding<BindableMediaRequest<ImageResource>> mImageRequestBinding;
-
- /** True if we want the image to fade in when it loads */
- private boolean mFadeIn;
-
- /** True if we want the image to reveal (scale) when it loads. When set to true, this
- * will take precedence over {@link #mFadeIn} */
- private final boolean mReveal;
-
- // The corner radius for drawing rounded corners around bitmap. The default value is zero
- // (no rounded corners)
- private final int mCornerRadius;
- private final Path mRoundedCornerClipPath;
- private int mClipPathWidth;
- private int mClipPathHeight;
-
- // A placeholder drawable that takes the spot of the image when it's loading. The default
- // setting is null (no placeholder).
- private final Drawable mPlaceholderDrawable;
- protected ImageResource mImageResource;
- private final Runnable mDisposeRunnable = new Runnable() {
- @Override
- public void run() {
- if (mImageRequestBinding.isBound()) {
- mDetachedRequestDescriptor = (ImageRequestDescriptor)
- mImageRequestBinding.getData().getDescriptor();
- }
- unbindView();
- releaseImageResource();
- }
- };
-
- private AsyncImageViewDelayLoader mDelayLoader;
- private ImageRequestDescriptor mDetachedRequestDescriptor;
-
- public AsyncImageView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mImageRequestBinding = BindingBase.createBinding(this);
- final TypedArray attr = context.obtainStyledAttributes(attrs, R.styleable.AsyncImageView,
- 0, 0);
- mFadeIn = attr.getBoolean(R.styleable.AsyncImageView_fadeIn, true);
- mReveal = attr.getBoolean(R.styleable.AsyncImageView_reveal, false);
- mPlaceholderDrawable = attr.getDrawable(R.styleable.AsyncImageView_placeholderDrawable);
- mCornerRadius = attr.getDimensionPixelSize(R.styleable.AsyncImageView_cornerRadius, 0);
- mRoundedCornerClipPath = new Path();
-
- attr.recycle();
- }
-
- /**
- * The main entrypoint for AsyncImageView to load image resource given an ImageRequestDescriptor
- * @param descriptor the request descriptor, or null if no image should be displayed
- */
- public void setImageResourceId(@Nullable final ImageRequestDescriptor descriptor) {
- final String requestKey = (descriptor == null) ? null : descriptor.getKey();
- if (mImageRequestBinding.isBound()) {
- if (TextUtils.equals(mImageRequestBinding.getData().getKey(), requestKey)) {
- // Don't re-request the bitmap if the new request is for the same resource.
- return;
- }
- unbindView();
- }
- setImage(null);
- resetTransientViewStates();
- if (!TextUtils.isEmpty(requestKey)) {
- maybeSetupPlaceholderDrawable(descriptor);
- final BindableMediaRequest<ImageResource> imageRequest =
- descriptor.buildAsyncMediaRequest(getContext(), this);
- requestImage(imageRequest);
- }
- }
-
- /**
- * Sets a delay loader that centrally manages image request delay loading logic.
- */
- public void setDelayLoader(final AsyncImageViewDelayLoader delayLoader) {
- Assert.isTrue(mDelayLoader == null);
- mDelayLoader = delayLoader;
- }
-
- /**
- * Called by the delay loader when we can resume image loading.
- */
- public void resumeLoading() {
- Assert.notNull(mDelayLoader);
- Assert.isTrue(mImageRequestBinding.isBound());
- MediaResourceManager.get().requestMediaResourceAsync(mImageRequestBinding.getData());
- }
-
- /**
- * Setup the placeholder drawable if:
- * 1. There's an image to be loaded AND
- * 2. We are given a placeholder drawable AND
- * 3. The descriptor provided us with source width and height.
- */
- private void maybeSetupPlaceholderDrawable(final ImageRequestDescriptor descriptor) {
- if (!TextUtils.isEmpty(descriptor.getKey()) && mPlaceholderDrawable != null) {
- if (descriptor.sourceWidth != ImageRequest.UNSPECIFIED_SIZE &&
- descriptor.sourceHeight != ImageRequest.UNSPECIFIED_SIZE) {
- // Set a transparent inset drawable to the foreground so it will mimick the final
- // size of the image, and use the background to show the actual placeholder
- // drawable.
- setImageDrawable(PlaceholderInsetDrawable.fromDrawable(
- new ColorDrawable(Color.TRANSPARENT),
- descriptor.sourceWidth, descriptor.sourceHeight));
- }
- setBackground(mPlaceholderDrawable);
- }
- }
-
- protected void setImage(final ImageResource resource) {
- setImage(resource, false /* isCached */);
- }
-
- protected void setImage(final ImageResource resource, final boolean isCached) {
- // Switch reference to the new ImageResource. Make sure we release the current
- // resource and addRef() on the new resource so that the underlying bitmaps don't
- // get leaked or get recycled by the bitmap cache.
- releaseImageResource();
- // Ensure that any pending dispose runnables get removed.
- ThreadUtil.getMainThreadHandler().removeCallbacks(mDisposeRunnable);
- // The drawable may require work to get if its a static object so try to only make this call
- // once.
- final Drawable drawable = (resource != null) ? resource.getDrawable(getResources()) : null;
- if (drawable != null) {
- mImageResource = resource;
- mImageResource.addRef();
- setImageDrawable(drawable);
- if (drawable instanceof FrameSequenceDrawable) {
- ((FrameSequenceDrawable) drawable).start();
- }
-
- if (getVisibility() == VISIBLE) {
- if (mReveal) {
- setVisibility(INVISIBLE);
- UiUtils.revealOrHideViewWithAnimation(this, VISIBLE, null);
- } else if (mFadeIn && !isCached) {
- // Hide initially to avoid flash.
- setAlpha(0F);
- animate().alpha(1F).start();
- }
- }
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- if (mImageResource instanceof GifImageResource) {
- LogUtil.v(TAG, "setImage size unknown -- it's a GIF");
- } else {
- LogUtil.v(TAG, "setImage size: " + mImageResource.getMediaSize() +
- " width: " + mImageResource.getBitmap().getWidth() +
- " heigh: " + mImageResource.getBitmap().getHeight());
- }
- }
- }
- invalidate();
- }
-
- private void requestImage(final BindableMediaRequest<ImageResource> request) {
- mImageRequestBinding.bind(request);
- if (mDelayLoader == null || !mDelayLoader.isDelayLoadingImage()) {
- MediaResourceManager.get().requestMediaResourceAsync(request);
- } else {
- mDelayLoader.registerView(this);
- }
- }
-
- @Override
- public void onMediaResourceLoaded(final MediaRequest<ImageResource> request,
- final ImageResource resource, final boolean isCached) {
- if (mImageResource != resource) {
- setImage(resource, isCached);
- }
- }
-
- @Override
- public void onMediaResourceLoadError(
- final MediaRequest<ImageResource> request, final Exception exception) {
- // Media load failed, unbind and reset bitmap to default.
- unbindView();
- setImage(null);
- }
-
- private void releaseImageResource() {
- final Drawable drawable = getDrawable();
- if (drawable instanceof FrameSequenceDrawable) {
- ((FrameSequenceDrawable) drawable).stop();
- ((FrameSequenceDrawable) drawable).destroy();
- }
- if (mImageResource != null) {
- mImageResource.release();
- mImageResource = null;
- }
- setImageDrawable(null);
- setBackground(null);
- }
-
- /**
- * Resets transient view states (eg. alpha, animations) before rebinding/reusing the view.
- */
- private void resetTransientViewStates() {
- clearAnimation();
- setAlpha(1F);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- // If it was recently removed, then cancel disposing, we're still using it.
- ThreadUtil.getMainThreadHandler().removeCallbacks(mDisposeRunnable);
-
- // When the image view gets detached and immediately re-attached, any fade-in animation
- // will be terminated, leaving the view in a semi-transparent state. Make sure we restore
- // alpha when the view is re-attached.
- if (mFadeIn) {
- setAlpha(1F);
- }
-
- // Check whether we are in a simple reuse scenario: detached from window, and reattached
- // later without rebinding. This may be done by containers such as the RecyclerView to
- // reuse the views. In this case, we would like to rebind the original image request.
- if (!mImageRequestBinding.isBound() && mDetachedRequestDescriptor != null) {
- setImageResourceId(mDetachedRequestDescriptor);
- }
- mDetachedRequestDescriptor = null;
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- // Dispose the bitmap, but if an AysncImageView is removed from the window, then quickly
- // re-added, we shouldn't dispose, so wait a short time before disposing
- ThreadUtil.getMainThreadHandler().postDelayed(mDisposeRunnable, DISPOSE_IMAGE_DELAY);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- // The base implementation does not honor the minimum sizes. We try to to honor it here.
-
- final int measuredWidth = getMeasuredWidth();
- final int measuredHeight = getMeasuredHeight();
- if (measuredWidth >= getMinimumWidth() || measuredHeight >= getMinimumHeight()) {
- // We are ok if either of the minimum sizes is honored. Note that satisfying both the
- // sizes may not be possible, depending on the aspect ratio of the image and whether
- // a maximum size has been specified. This implementation only tries to handle the case
- // where both the minimum sizes are not being satisfied.
- return;
- }
-
- if (!getAdjustViewBounds()) {
- // The base implementation is reasonable in this case. If the view bounds cannot be
- // changed, it is not possible to satisfy the minimum sizes anyway.
- return;
- }
-
- final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
- final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
- if (widthSpecMode == MeasureSpec.EXACTLY && heightSpecMode == MeasureSpec.EXACTLY) {
- // The base implementation is reasonable in this case.
- return;
- }
-
- int width = measuredWidth;
- int height = measuredHeight;
- // Get the minimum sizes that will honor other constraints as well.
- final int minimumWidth = resolveSize(
- getMinimumWidth(), getMaxWidth(), widthMeasureSpec);
- final int minimumHeight = resolveSize(
- getMinimumHeight(), getMaxHeight(), heightMeasureSpec);
- final float aspectRatio = measuredWidth / (float) measuredHeight;
- if (aspectRatio == 0) {
- // If the image is (close to) infinitely high, there is not much we can do.
- return;
- }
-
- if (width < minimumWidth) {
- height = resolveSize((int) (minimumWidth / aspectRatio),
- getMaxHeight(), heightMeasureSpec);
- width = (int) (height * aspectRatio);
- }
-
- if (height < minimumHeight) {
- width = resolveSize((int) (minimumHeight * aspectRatio),
- getMaxWidth(), widthMeasureSpec);
- height = (int) (width / aspectRatio);
- }
-
- setMeasuredDimension(width, height);
- }
-
- private static int resolveSize(int desiredSize, int maxSize, int measureSpec) {
- final int specMode = MeasureSpec.getMode(measureSpec);
- final int specSize = MeasureSpec.getSize(measureSpec);
- switch(specMode) {
- case MeasureSpec.UNSPECIFIED:
- return Math.min(desiredSize, maxSize);
-
- case MeasureSpec.AT_MOST:
- return Math.min(Math.min(desiredSize, specSize), maxSize);
-
- default:
- Assert.fail("Unreachable");
- return specSize;
- }
- }
-
- @Override
- protected void onDraw(final Canvas canvas) {
- if (mCornerRadius > 0) {
- final int currentWidth = this.getWidth();
- final int currentHeight = this.getHeight();
- if (mClipPathWidth != currentWidth || mClipPathHeight != currentHeight) {
- final RectF rect = new RectF(0, 0, currentWidth, currentHeight);
- mRoundedCornerClipPath.reset();
- mRoundedCornerClipPath.addRoundRect(rect, mCornerRadius, mCornerRadius,
- Path.Direction.CW);
- mClipPathWidth = currentWidth;
- mClipPathHeight = currentHeight;
- }
-
- final int saveCount = canvas.getSaveCount();
- canvas.save();
- canvas.clipPath(mRoundedCornerClipPath);
- super.onDraw(canvas);
- canvas.restoreToCount(saveCount);
- } else {
- super.onDraw(canvas);
- }
- }
-
- private void unbindView() {
- if (mImageRequestBinding.isBound()) {
- mImageRequestBinding.unbind();
- if (mDelayLoader != null) {
- mDelayLoader.unregisterView(this);
- }
- }
- }
-
- /**
- * As a performance optimization, the consumer of the AsyncImageView may opt to delay loading
- * the image when it's busy doing other things (such as when a list view is scrolling). In
- * order to do this, the consumer can create a new AsyncImageViewDelayLoader instance to be
- * shared among all relevant AsyncImageViews (through setDelayLoader() method), and call
- * onStartDelayLoading() and onStopDelayLoading() to start and stop delay loading, respectively.
- */
- public static class AsyncImageViewDelayLoader {
- private boolean mShouldDelayLoad;
- private final HashSet<AsyncImageView> mAttachedViews;
-
- public AsyncImageViewDelayLoader() {
- mAttachedViews = new HashSet<AsyncImageView>();
- }
-
- private void registerView(final AsyncImageView view) {
- mAttachedViews.add(view);
- }
-
- private void unregisterView(final AsyncImageView view) {
- mAttachedViews.remove(view);
- }
-
- public boolean isDelayLoadingImage() {
- return mShouldDelayLoad;
- }
-
- /**
- * Called by the consumer of this view to delay loading images
- */
- public void onDelayLoading() {
- // Don't need to explicitly tell the AsyncImageView to stop loading since
- // ImageRequests are not cancellable.
- mShouldDelayLoad = true;
- }
-
- /**
- * Called by the consumer of this view to resume loading images
- */
- public void onResumeLoading() {
- if (mShouldDelayLoad) {
- mShouldDelayLoad = false;
-
- // Notify all attached views to resume loading.
- for (final AsyncImageView view : mAttachedViews) {
- view.resumeLoading();
- }
- mAttachedViews.clear();
- }
- }
- }
-}
diff --git a/src/com/android/messaging/ui/AttachmentPreview.java b/src/com/android/messaging/ui/AttachmentPreview.java
deleted file mode 100644
index 7eea14b..0000000
--- a/src/com/android/messaging/ui/AttachmentPreview.java
+++ /dev/null
@@ -1,331 +0,0 @@
-/*
- * 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;
-
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageButton;
-import android.widget.ScrollView;
-
-import com.android.messaging.R;
-import com.android.messaging.annotation.VisibleForAnimation;
-import com.android.messaging.datamodel.data.DraftMessageData;
-import com.android.messaging.datamodel.data.MediaPickerMessagePartData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.PendingAttachmentData;
-import com.android.messaging.ui.MultiAttachmentLayout.OnAttachmentClickListener;
-import com.android.messaging.ui.animation.PopupTransitionAnimation;
-import com.android.messaging.ui.conversation.ComposeMessageView;
-import com.android.messaging.ui.conversation.ConversationFragment;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ThreadUtil;
-import com.android.messaging.util.UiUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class AttachmentPreview extends ScrollView implements OnAttachmentClickListener {
- private FrameLayout mAttachmentView;
- private ComposeMessageView mComposeMessageView;
- private ImageButton mCloseButton;
- private int mAnimatedHeight = -1;
- private Animator mCloseGapAnimator;
- private boolean mPendingFirstUpdate;
- private Handler mHandler;
- private Runnable mHideRunnable;
- private boolean mPendingHideCanceled;
-
- private static final int CLOSE_BUTTON_REVEAL_STAGGER_MILLIS = 300;
-
- public AttachmentPreview(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mHandler = new Handler(Looper.getMainLooper());
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mCloseButton = (ImageButton) findViewById(R.id.close_button);
- mCloseButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View view) {
- mComposeMessageView.clearAttachments();
- }
- });
-
- mAttachmentView = (FrameLayout) findViewById(R.id.attachment_view);
-
- // The attachment preview is a scroll view so that it can show the bottom portion of the
- // attachment whenever the space is tight (e.g. when in landscape mode). Per design
- // request we'd like to make the attachment view always scrolled to the bottom.
- addOnLayoutChangeListener(new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(final View v, final int left, final int top, final int right,
- final int bottom, final int oldLeft, final int oldTop, final int oldRight,
- final int oldBottom) {
- post(new Runnable() {
- @Override
- public void run() {
- final int childCount = getChildCount();
- if (childCount > 0) {
- final View lastChild = getChildAt(childCount - 1);
- scrollTo(getScrollX(), lastChild.getBottom() - getHeight());
- }
- }
- });
- }
- });
- mPendingFirstUpdate = true;
- }
-
- public void setComposeMessageView(final ComposeMessageView composeMessageView) {
- mComposeMessageView = composeMessageView;
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mAnimatedHeight >= 0) {
- setMeasuredDimension(getMeasuredWidth(), mAnimatedHeight);
- }
- }
-
- private void cancelPendingHide() {
- mPendingHideCanceled = true;
- }
-
- public void hideAttachmentPreview() {
- if (getVisibility() != GONE) {
- UiUtils.revealOrHideViewWithAnimation(mCloseButton, GONE,
- null /* onFinishRunnable */);
- startCloseGapAnimationOnAttachmentClear();
-
- if (mAttachmentView.getChildCount() > 0) {
- mPendingHideCanceled = false;
- final View viewToHide = mAttachmentView.getChildCount() > 1 ?
- mAttachmentView : mAttachmentView.getChildAt(0);
- UiUtils.revealOrHideViewWithAnimation(viewToHide, INVISIBLE,
- new Runnable() {
- @Override
- public void run() {
- // Only hide if we are didn't get overruled by showing
- if (!mPendingHideCanceled) {
- mAttachmentView.removeAllViews();
- setVisibility(GONE);
- }
- }
- });
- } else {
- mAttachmentView.removeAllViews();
- setVisibility(GONE);
- }
- }
- }
-
- // returns true if we have attachments
- public boolean onAttachmentsChanged(final DraftMessageData draftMessageData) {
- final boolean isFirstUpdate = mPendingFirstUpdate;
- final List<MessagePartData> attachments = draftMessageData.getReadOnlyAttachments();
- final List<PendingAttachmentData> pendingAttachments =
- draftMessageData.getReadOnlyPendingAttachments();
-
- // Any change in attachments would invalidate the animated height animation.
- cancelCloseGapAnimation();
- mPendingFirstUpdate = false;
-
- final int combinedAttachmentCount = attachments.size() + pendingAttachments.size();
- mCloseButton.setContentDescription(getResources()
- .getQuantityString(R.plurals.attachment_preview_close_content_description,
- combinedAttachmentCount));
- if (combinedAttachmentCount == 0) {
- mHideRunnable = new Runnable() {
- @Override
- public void run() {
- mHideRunnable = null;
- // Only start the hiding if there are still no attachments
- if (attachments.size() + pendingAttachments.size() == 0) {
- hideAttachmentPreview();
- }
- }
- };
- if (draftMessageData.isSending()) {
- // Wait to hide until the message is ready to start animating
- // We'll execute immediately when the animation triggers
- mHandler.postDelayed(mHideRunnable,
- ConversationFragment.MESSAGE_ANIMATION_MAX_WAIT);
- } else {
- // Run immediately when clearing attachments
- mHideRunnable.run();
- }
- return false;
- }
-
- cancelPendingHide(); // We're showing
- if (getVisibility() != VISIBLE) {
- setVisibility(VISIBLE);
- mAttachmentView.setVisibility(VISIBLE);
-
- // Don't animate in the close button if this is the first update after view creation.
- // This is the initial draft load from database for pre-existing drafts.
- if (!isFirstUpdate) {
- // Reveal the close button after the view animates in.
- mCloseButton.setVisibility(INVISIBLE);
- ThreadUtil.getMainThreadHandler().postDelayed(new Runnable() {
- @Override
- public void run() {
- UiUtils.revealOrHideViewWithAnimation(mCloseButton, VISIBLE,
- null /* onFinishRunnable */);
- }
- }, UiUtils.MEDIAPICKER_TRANSITION_DURATION + CLOSE_BUTTON_REVEAL_STAGGER_MILLIS);
- }
- }
-
- // Merge the pending attachment list with real attachment. Design would prefer these be
- // in LIFO order user can see added images past the 5th one but we also want them to be in
- // order and we want it to be WYSIWYG.
- final List<MessagePartData> combinedAttachments = new ArrayList<>();
- combinedAttachments.addAll(attachments);
- combinedAttachments.addAll(pendingAttachments);
-
- final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
- if (combinedAttachmentCount > 1) {
- MultiAttachmentLayout multiAttachmentLayout = null;
- Rect transitionRect = null;
- if (mAttachmentView.getChildCount() > 0) {
- final View firstChild = mAttachmentView.getChildAt(0);
- if (firstChild instanceof MultiAttachmentLayout) {
- Assert.equals(1, mAttachmentView.getChildCount());
- multiAttachmentLayout = (MultiAttachmentLayout) firstChild;
- multiAttachmentLayout.bindAttachments(combinedAttachments,
- null /* transitionRect */, combinedAttachmentCount);
- } else {
- transitionRect = new Rect(firstChild.getLeft(), firstChild.getTop(),
- firstChild.getRight(), firstChild.getBottom());
- }
- }
- if (multiAttachmentLayout == null) {
- multiAttachmentLayout = AttachmentPreviewFactory.createMultiplePreview(
- getContext(), this);
- multiAttachmentLayout.bindAttachments(combinedAttachments, transitionRect,
- combinedAttachmentCount);
- mAttachmentView.removeAllViews();
- mAttachmentView.addView(multiAttachmentLayout);
- }
- } else {
- final MessagePartData attachment = combinedAttachments.get(0);
- boolean shouldAnimate = true;
- if (mAttachmentView.getChildCount() > 0) {
- // If we are going from N->1 attachments, try to use the current bounds
- // bounds as the starting rect.
- shouldAnimate = false;
- final View firstChild = mAttachmentView.getChildAt(0);
- if (firstChild instanceof MultiAttachmentLayout &&
- attachment instanceof MediaPickerMessagePartData) {
- final View leftoverView = ((MultiAttachmentLayout) firstChild)
- .findViewForAttachment(attachment);
- if (leftoverView != null) {
- final Rect currentRect = UiUtils.getMeasuredBoundsOnScreen(leftoverView);
- if (!currentRect.isEmpty() &&
- attachment instanceof MediaPickerMessagePartData) {
- ((MediaPickerMessagePartData) attachment).setStartRect(currentRect);
- shouldAnimate = true;
- }
- }
- }
- }
- mAttachmentView.removeAllViews();
- final View attachmentView = AttachmentPreviewFactory.createAttachmentPreview(
- layoutInflater, attachment, mAttachmentView,
- AttachmentPreviewFactory.TYPE_SINGLE, true /* startImageRequest */, this);
- if (attachmentView != null) {
- mAttachmentView.addView(attachmentView);
- if (shouldAnimate) {
- tryAnimateViewIn(attachment, attachmentView);
- }
- }
- }
- return true;
- }
-
- public void onMessageAnimationStart() {
- if (mHideRunnable == null) {
- return;
- }
-
- // Run the hide animation at the same time as the message animation
- mHandler.removeCallbacks(mHideRunnable);
- setVisibility(View.INVISIBLE);
- mHideRunnable.run();
- }
-
- static void tryAnimateViewIn(final MessagePartData attachmentData, final View view) {
- if (attachmentData instanceof MediaPickerMessagePartData) {
- final Rect startRect = ((MediaPickerMessagePartData) attachmentData).getStartRect();
- new PopupTransitionAnimation(startRect, view).startAfterLayoutComplete();
- }
- }
-
- @VisibleForAnimation
- public void setAnimatedHeight(final int animatedHeight) {
- if (mAnimatedHeight != animatedHeight) {
- mAnimatedHeight = animatedHeight;
- requestLayout();
- }
- }
-
- /**
- * Kicks off an animation to animate the layout change for closing the gap between the
- * message list and the compose message box when the attachments are cleared.
- */
- private void startCloseGapAnimationOnAttachmentClear() {
- // Cancel existing animation.
- cancelCloseGapAnimation();
- mCloseGapAnimator = ObjectAnimator.ofInt(this, "animatedHeight", getHeight(), 0);
- mCloseGapAnimator.start();
- }
-
- private void cancelCloseGapAnimation() {
- if (mCloseGapAnimator != null) {
- mCloseGapAnimator.cancel();
- mCloseGapAnimator = null;
- }
- mAnimatedHeight = -1;
- }
-
- @Override
- public boolean onAttachmentClick(final MessagePartData attachment,
- final Rect viewBoundsOnScreen, final boolean longPress) {
- if (longPress) {
- mComposeMessageView.onAttachmentPreviewLongClicked();
- return true;
- }
-
- if (!(attachment instanceof PendingAttachmentData) && attachment.isImage()) {
- mComposeMessageView.displayPhoto(attachment.getContentUri(), viewBoundsOnScreen);
- return true;
- }
- return false;
- }
-}
diff --git a/src/com/android/messaging/ui/AttachmentPreviewFactory.java b/src/com/android/messaging/ui/AttachmentPreviewFactory.java
deleted file mode 100644
index ed5d4d7..0000000
--- a/src/com/android/messaging/ui/AttachmentPreviewFactory.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnLongClickListener;
-import android.view.ViewGroup;
-import android.widget.FrameLayout.LayoutParams;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.PendingAttachmentData;
-import com.android.messaging.datamodel.data.PersonItemData;
-import com.android.messaging.datamodel.data.VCardContactItemData;
-import com.android.messaging.datamodel.media.FileImageRequestDescriptor;
-import com.android.messaging.datamodel.media.ImageRequest;
-import com.android.messaging.datamodel.media.ImageRequestDescriptor;
-import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
-import com.android.messaging.ui.MultiAttachmentLayout.OnAttachmentClickListener;
-import com.android.messaging.ui.PersonItemView.PersonItemViewListener;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.UiUtils;
-import com.android.messaging.util.UriUtil;
-
-/**
- * A view factory that creates previews for single/multiple attachments.
- */
-public class AttachmentPreviewFactory {
- /** Standalone attachment preview */
- public static final int TYPE_SINGLE = 1;
-
- /** Attachment preview displayed in a multi-attachment layout */
- public static final int TYPE_MULTIPLE = 2;
-
- /** Attachment preview displayed in the attachment chooser grid view */
- public static final int TYPE_CHOOSER_GRID = 3;
-
- public static View createAttachmentPreview(final LayoutInflater layoutInflater,
- final MessagePartData attachmentData, final ViewGroup parent,
- final int viewType, final boolean startImageRequest,
- @Nullable final OnAttachmentClickListener clickListener) {
- final String contentType = attachmentData.getContentType();
- View attachmentView = null;
- if (attachmentData instanceof PendingAttachmentData) {
- attachmentView = createPendingAttachmentPreview(layoutInflater, parent,
- (PendingAttachmentData) attachmentData);
- } else if (ContentType.isImageType(contentType)) {
- attachmentView = createImagePreview(layoutInflater, attachmentData, parent, viewType,
- startImageRequest);
- } else if (ContentType.isAudioType(contentType)) {
- attachmentView = createAudioPreview(layoutInflater, attachmentData, parent, viewType);
- } else if (ContentType.isVideoType(contentType)) {
- attachmentView = createVideoPreview(layoutInflater, attachmentData, parent, viewType);
- } else if (ContentType.isVCardType(contentType)) {
- attachmentView = createVCardPreview(layoutInflater, attachmentData, parent, viewType);
- } else {
- Assert.fail("unsupported attachment type: " + contentType);
- return null;
- }
-
- // Some views have a caption, set the text/visibility if one exists
- final TextView captionView = (TextView) attachmentView.findViewById(R.id.caption);
- if (captionView != null) {
- final String caption = attachmentData.getText();
- captionView.setVisibility(TextUtils.isEmpty(caption) ? View.GONE : View.VISIBLE);
- captionView.setText(caption);
- }
-
- if (attachmentView != null && clickListener != null) {
- attachmentView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View view) {
- final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(view);
- clickListener.onAttachmentClick(attachmentData, bounds,
- false /* longPress */);
- }
- });
- attachmentView.setOnLongClickListener(new OnLongClickListener() {
- @Override
- public boolean onLongClick(final View view) {
- final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(view);
- return clickListener.onAttachmentClick(attachmentData, bounds,
- true /* longPress */);
- }
- });
- }
- return attachmentView;
- }
-
- public static MultiAttachmentLayout createMultiplePreview(final Context context,
- final OnAttachmentClickListener listener) {
- final MultiAttachmentLayout multiAttachmentLayout =
- new MultiAttachmentLayout(context, null);
- final ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
- multiAttachmentLayout.setLayoutParams(layoutParams);
- multiAttachmentLayout.setOnAttachmentClickListener(listener);
- return multiAttachmentLayout;
- }
-
- public static ImageRequestDescriptor getImageRequestDescriptorForAttachment(
- final MessagePartData attachmentData, final int desiredWidth, final int desiredHeight) {
- final Uri uri = attachmentData.getContentUri();
- final String contentType = attachmentData.getContentType();
- if (ContentType.isImageType(contentType)) {
- final String filePath = UriUtil.getFilePathFromUri(uri);
- if (filePath != null) {
- return new FileImageRequestDescriptor(filePath, desiredWidth, desiredHeight,
- attachmentData.getWidth(), attachmentData.getHeight(),
- false /* canUseThumbnail */, true /* allowCompression */,
- false /* isStatic */);
- } else {
- return new UriImageRequestDescriptor(uri, desiredWidth, desiredHeight,
- attachmentData.getWidth(), attachmentData.getHeight(),
- true /* allowCompression */, false /* isStatic */, false /*cropToCircle*/,
- ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
- ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
- }
- }
- return null;
- }
-
- private static View createImagePreview(final LayoutInflater layoutInflater,
- final MessagePartData attachmentData, final ViewGroup parent,
- final int viewType, final boolean startImageRequest) {
- int layoutId = R.layout.attachment_single_image;
- switch (viewType) {
- case AttachmentPreviewFactory.TYPE_SINGLE:
- layoutId = R.layout.attachment_single_image;
- break;
- case AttachmentPreviewFactory.TYPE_MULTIPLE:
- layoutId = R.layout.attachment_multiple_image;
- break;
- case AttachmentPreviewFactory.TYPE_CHOOSER_GRID:
- layoutId = R.layout.attachment_chooser_image;
- break;
- default:
- Assert.fail("unsupported attachment view type!");
- break;
- }
- final View view = layoutInflater.inflate(layoutId, parent, false /* attachToRoot */);
- final AsyncImageView imageView = (AsyncImageView) view.findViewById(
- R.id.attachment_image_view);
- int maxWidth = imageView.getMaxWidth();
- int maxHeight = imageView.getMaxHeight();
- if (viewType == TYPE_CHOOSER_GRID) {
- final Resources resources = layoutInflater.getContext().getResources();
- maxWidth = maxHeight = resources.getDimensionPixelSize(
- R.dimen.attachment_grid_image_cell_size);
- }
- if (maxWidth <= 0 || maxWidth == Integer.MAX_VALUE) {
- maxWidth = ImageRequest.UNSPECIFIED_SIZE;
- }
- if (maxHeight <= 0 || maxHeight == Integer.MAX_VALUE) {
- maxHeight = ImageRequest.UNSPECIFIED_SIZE;
- }
- if (startImageRequest) {
- imageView.setImageResourceId(getImageRequestDescriptorForAttachment(attachmentData,
- maxWidth, maxHeight));
- }
- imageView.setContentDescription(
- parent.getResources().getString(R.string.message_image_content_description));
- return view;
- }
-
- private static View createPendingAttachmentPreview(final LayoutInflater layoutInflater,
- final ViewGroup parent, final PendingAttachmentData attachmentData) {
- final View pendingItemView = layoutInflater.inflate(R.layout.attachment_pending_item,
- parent, false);
- final ImageView imageView = (ImageView)
- pendingItemView.findViewById(R.id.pending_item_view);
- final ViewGroup.LayoutParams layoutParams = imageView.getLayoutParams();
- final int defaultSize = layoutInflater.getContext().getResources().getDimensionPixelSize(
- R.dimen.pending_attachment_size);
- layoutParams.width = attachmentData.getWidth() == MessagePartData.UNSPECIFIED_SIZE ?
- defaultSize : attachmentData.getWidth();
- layoutParams.height = attachmentData.getHeight() == MessagePartData.UNSPECIFIED_SIZE ?
- defaultSize : attachmentData.getHeight();
- return pendingItemView;
- }
-
- private static View createVCardPreview(final LayoutInflater layoutInflater,
- final MessagePartData attachmentData, final ViewGroup parent,
- final int viewType) {
- int layoutId = R.layout.attachment_single_vcard;
- switch (viewType) {
- case AttachmentPreviewFactory.TYPE_SINGLE:
- layoutId = R.layout.attachment_single_vcard;
- break;
- case AttachmentPreviewFactory.TYPE_MULTIPLE:
- layoutId = R.layout.attachment_multiple_vcard;
- break;
- case AttachmentPreviewFactory.TYPE_CHOOSER_GRID:
- layoutId = R.layout.attachment_chooser_vcard;
- break;
- default:
- Assert.fail("unsupported attachment view type!");
- break;
- }
- final View view = layoutInflater.inflate(layoutId, parent, false /* attachToRoot */);
- final PersonItemView vcardPreview = (PersonItemView) view.findViewById(
- R.id.vcard_attachment_view);
- vcardPreview.setAvatarOnly(viewType != AttachmentPreviewFactory.TYPE_SINGLE);
- vcardPreview.bind(DataModel.get().createVCardContactItemData(layoutInflater.getContext(),
- attachmentData));
- vcardPreview.setListener(new PersonItemViewListener() {
- @Override
- public void onPersonClicked(final PersonItemData data) {
- Assert.isTrue(data instanceof VCardContactItemData);
- final VCardContactItemData vCardData = (VCardContactItemData) data;
- if (vCardData.hasValidVCard()) {
- final Uri vCardUri = vCardData.getVCardUri();
- UIIntents.get().launchVCardDetailActivity(vcardPreview.getContext(), vCardUri);
- }
- }
-
- @Override
- public boolean onPersonLongClicked(final PersonItemData data) {
- return false;
- }
- });
- return view;
- }
-
- private static View createAudioPreview(final LayoutInflater layoutInflater,
- final MessagePartData attachmentData, final ViewGroup parent,
- final int viewType) {
- int layoutId = R.layout.attachment_single_audio;
- switch (viewType) {
- case AttachmentPreviewFactory.TYPE_SINGLE:
- layoutId = R.layout.attachment_single_audio;
- break;
- case AttachmentPreviewFactory.TYPE_MULTIPLE:
- layoutId = R.layout.attachment_multiple_audio;
- break;
- case AttachmentPreviewFactory.TYPE_CHOOSER_GRID:
- layoutId = R.layout.attachment_chooser_audio;
- break;
- default:
- Assert.fail("unsupported attachment view type!");
- break;
- }
- final View view = layoutInflater.inflate(layoutId, parent, false /* attachToRoot */);
- final AudioAttachmentView audioView = (AudioAttachmentView)
- view.findViewById(R.id.audio_attachment_view);
- audioView.bindMessagePartData(attachmentData, false /* incoming */);
- return view;
- }
-
- private static View createVideoPreview(final LayoutInflater layoutInflater,
- final MessagePartData attachmentData, final ViewGroup parent,
- final int viewType) {
- int layoutId = R.layout.attachment_single_video;
- switch (viewType) {
- case AttachmentPreviewFactory.TYPE_SINGLE:
- layoutId = R.layout.attachment_single_video;
- break;
- case AttachmentPreviewFactory.TYPE_MULTIPLE:
- layoutId = R.layout.attachment_multiple_video;
- break;
- case AttachmentPreviewFactory.TYPE_CHOOSER_GRID:
- layoutId = R.layout.attachment_chooser_video;
- break;
- default:
- Assert.fail("unsupported attachment view type!");
- break;
- }
- final VideoThumbnailView videoThumbnail = (VideoThumbnailView) layoutInflater.inflate(
- layoutId, parent, false /* attachToRoot */);
- videoThumbnail.setSource(attachmentData, false /* incomingMessage */);
- return videoThumbnail;
- }
-}
diff --git a/src/com/android/messaging/ui/AudioAttachmentPlayPauseButton.java b/src/com/android/messaging/ui/AudioAttachmentPlayPauseButton.java
deleted file mode 100644
index 724c4fe..0000000
--- a/src/com/android/messaging/ui/AudioAttachmentPlayPauseButton.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.ImageView;
-import android.widget.ViewSwitcher;
-
-import com.android.messaging.R;
-
-/**
- * Shows a tinted play pause button.
- */
-public class AudioAttachmentPlayPauseButton extends ViewSwitcher {
- private ImageView mPlayButton;
- private ImageView mPauseButton;
-
- private boolean mShowAsIncoming;
-
- public AudioAttachmentPlayPauseButton(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mPlayButton = (ImageView) findViewById(R.id.play_button);
- mPauseButton = (ImageView) findViewById(R.id.pause_button);
- updateAppearance();
- }
-
- public void setVisualStyle(final boolean showAsIncoming) {
- if (mShowAsIncoming != showAsIncoming) {
- mShowAsIncoming = showAsIncoming;
- updateAppearance();
- }
- }
-
- private void updateAppearance() {
- mPlayButton.setImageDrawable(ConversationDrawables.get()
- .getPlayButtonDrawable(mShowAsIncoming));
- mPauseButton.setImageDrawable(ConversationDrawables.get()
- .getPauseButtonDrawable(mShowAsIncoming));
- }
-}
diff --git a/src/com/android/messaging/ui/AudioAttachmentView.java b/src/com/android/messaging/ui/AudioAttachmentView.java
deleted file mode 100644
index bb649b0..0000000
--- a/src/com/android/messaging/ui/AudioAttachmentView.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.RectF;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnCompletionListener;
-import android.media.MediaPlayer.OnErrorListener;
-import android.media.MediaPlayer.OnPreparedListener;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.ui.mediapicker.PausableChronometer;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.UiUtils;
-
-/**
- * A reusable widget that hosts an audio player for audio attachment playback. This widget is used
- * by both the media picker and the conversation message view to show audio attachments.
- */
-public class AudioAttachmentView extends LinearLayout {
- /** The normal layout mode where we have the play button, timer and progress bar */
- private static final int LAYOUT_MODE_NORMAL = 0;
-
- /** The compact layout mode with only the play button and the timer beneath it. Suitable
- * for displaying in limited space such as multi-attachment layout */
- private static final int LAYOUT_MODE_COMPACT = 1;
-
- /** The sub-compact layout mode with only the play button. */
- private static final int LAYOUT_MODE_SUB_COMPACT = 2;
-
- private static final int PLAY_BUTTON = 0;
- private static final int PAUSE_BUTTON = 1;
-
- private AudioAttachmentPlayPauseButton mPlayPauseButton;
- private PausableChronometer mChronometer;
- private AudioPlaybackProgressBar mProgressBar;
- private MediaPlayer mMediaPlayer;
-
- private Uri mDataSourceUri;
-
- // The corner radius for drawing rounded corners. The default value is zero (no rounded corners)
- private final int mCornerRadius;
- private final Path mRoundedCornerClipPath;
- private int mClipPathWidth;
- private int mClipPathHeight;
-
- // Indicates whether the attachment view is to be styled as a part of an incoming message.
- private boolean mShowAsIncoming;
-
- private boolean mPrepared;
- private boolean mPlaybackFinished;
- private final int mMode;
-
- public AudioAttachmentView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- final TypedArray typedAttributes =
- context.obtainStyledAttributes(attrs, R.styleable.AudioAttachmentView);
- mMode = typedAttributes.getInt(R.styleable.AudioAttachmentView_layoutMode,
- LAYOUT_MODE_NORMAL);
- final LayoutInflater inflater = LayoutInflater.from(getContext());
- inflater.inflate(R.layout.audio_attachment_view, this, true);
- typedAttributes.recycle();
-
- setWillNotDraw(mMode != LAYOUT_MODE_SUB_COMPACT);
- mRoundedCornerClipPath = new Path();
- mCornerRadius = context.getResources().getDimensionPixelSize(
- R.dimen.conversation_list_image_preview_corner_radius);
- setContentDescription(context.getString(R.string.audio_attachment_content_description));
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mPlayPauseButton = (AudioAttachmentPlayPauseButton) findViewById(R.id.play_pause_button);
- mChronometer = (PausableChronometer) findViewById(R.id.timer);
- mProgressBar = (AudioPlaybackProgressBar) findViewById(R.id.progress);
- mPlayPauseButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View v) {
- setupMediaPlayer();
- if (mMediaPlayer != null && mPrepared) {
- if (mMediaPlayer.isPlaying()) {
- mMediaPlayer.pause();
- mChronometer.pause();
- mProgressBar.pause();
- } else {
- playAudio();
- }
- }
- updatePlayPauseButtonState();
- }
- });
- updatePlayPauseButtonState();
- initializeViewsForMode();
- }
-
- /**
- * Bind the audio attachment view with a MessagePartData.
- * @param incoming indicates whether the attachment view is to be styled as a part of an
- * incoming message.
- */
- public void bindMessagePartData(final MessagePartData messagePartData,
- final boolean incoming) {
- Assert.isTrue(messagePartData == null ||
- ContentType.isAudioType(messagePartData.getContentType()));
- final Uri contentUri = (messagePartData == null) ? null : messagePartData.getContentUri();
- bind(contentUri, incoming);
- }
-
- public void bind(final Uri dataSourceUri, final boolean incoming) {
- final String currentUriString = (mDataSourceUri == null) ? "" : mDataSourceUri.toString();
- final String newUriString = (dataSourceUri == null) ? "" : dataSourceUri.toString();
- mShowAsIncoming = incoming;
- if (!TextUtils.equals(currentUriString, newUriString)) {
- mDataSourceUri = dataSourceUri;
- resetToZeroState();
- }
- }
-
- private void playAudio() {
- Assert.notNull(mMediaPlayer);
- if (mPlaybackFinished) {
- mMediaPlayer.seekTo(0);
- mChronometer.restart();
- mProgressBar.restart();
- mPlaybackFinished = false;
- } else {
- mChronometer.resume();
- mProgressBar.resume();
- }
- mMediaPlayer.start();
- }
-
- private void onAudioReplayError(final int what, final int extra, final Exception exception) {
- if (exception == null) {
- LogUtil.e(LogUtil.BUGLE_TAG, "audio replay failed, what=" + what +
- ", extra=" + extra);
- } else {
- LogUtil.e(LogUtil.BUGLE_TAG, "audio replay failed, exception=" + exception);
- }
- UiUtils.showToastAtBottom(R.string.audio_recording_replay_failed);
- releaseMediaPlayer();
- }
-
- private void setupMediaPlayer() {
- Assert.notNull(mDataSourceUri);
- if (mMediaPlayer == null) {
- Assert.isTrue(!mPrepared);
- mMediaPlayer = new MediaPlayer();
- try {
- mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- mMediaPlayer.setDataSource(Factory.get().getApplicationContext(), mDataSourceUri);
- mMediaPlayer.setOnCompletionListener(new OnCompletionListener() {
- @Override
- public void onCompletion(final MediaPlayer mp) {
- updatePlayPauseButtonState();
- mChronometer.reset();
- mChronometer.setBase(SystemClock.elapsedRealtime() -
- mMediaPlayer.getDuration());
- mProgressBar.reset();
-
- mPlaybackFinished = true;
- }
- });
-
- mMediaPlayer.setOnPreparedListener(new OnPreparedListener() {
- @Override
- public void onPrepared(final MediaPlayer mp) {
- // Set base on the chronometer so we can show the full length of the audio.
- mChronometer.setBase(SystemClock.elapsedRealtime() -
- mMediaPlayer.getDuration());
- mProgressBar.setDuration(mMediaPlayer.getDuration());
- mMediaPlayer.seekTo(0);
- mPrepared = true;
- }
- });
-
- mMediaPlayer.setOnErrorListener(new OnErrorListener() {
- @Override
- public boolean onError(final MediaPlayer mp, final int what, final int extra) {
- onAudioReplayError(what, extra, null);
- return true;
- }
- });
- mMediaPlayer.prepareAsync();
- } catch (final Exception exception) {
- onAudioReplayError(0, 0, exception);
- releaseMediaPlayer();
- }
- }
- }
-
- private void releaseMediaPlayer() {
- if (mMediaPlayer != null) {
- mMediaPlayer.release();
- mMediaPlayer = null;
- mPrepared = false;
- mPlaybackFinished = false;
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- releaseMediaPlayer();
- }
-
- @Override
- protected void onDraw(final Canvas canvas) {
- if (mMode != LAYOUT_MODE_SUB_COMPACT) {
- return;
- }
-
- final int currentWidth = this.getWidth();
- final int currentHeight = this.getHeight();
- if (mClipPathWidth != currentWidth || mClipPathHeight != currentHeight) {
- final RectF rect = new RectF(0, 0, currentWidth, currentHeight);
- mRoundedCornerClipPath.reset();
- mRoundedCornerClipPath.addRoundRect(rect, mCornerRadius, mCornerRadius,
- Path.Direction.CW);
- mClipPathWidth = currentWidth;
- mClipPathHeight = currentHeight;
- }
-
- canvas.clipPath(mRoundedCornerClipPath);
- super.onDraw(canvas);
- }
-
- private void updatePlayPauseButtonState() {
- if (mMediaPlayer == null || !mMediaPlayer.isPlaying()) {
- mPlayPauseButton.setDisplayedChild(PLAY_BUTTON);
- } else {
- mPlayPauseButton.setDisplayedChild(PAUSE_BUTTON);
- }
- }
-
- private void resetToZeroState() {
- // Release the media player so it may be set up with the new audio source.
- releaseMediaPlayer();
- mChronometer.reset();
- mProgressBar.reset();
- updateVisualStyle();
-
- if (mDataSourceUri != null) {
- // Re-ensure the media player, so we can read the duration of the audio.
- setupMediaPlayer();
- }
- }
-
- private void updateVisualStyle() {
- if (mMode == LAYOUT_MODE_SUB_COMPACT) {
- // Sub-compact mode has static visual appearance already set up during initialization.
- return;
- }
-
- if (mShowAsIncoming) {
- mChronometer.setTextColor(getResources().getColor(R.color.message_text_color_incoming));
- } else {
- mChronometer.setTextColor(getResources().getColor(R.color.message_text_color_outgoing));
- }
- mProgressBar.setVisualStyle(mShowAsIncoming);
- mPlayPauseButton.setVisualStyle(mShowAsIncoming);
- updatePlayPauseButtonState();
- }
-
- private void initializeViewsForMode() {
- switch (mMode) {
- case LAYOUT_MODE_NORMAL:
- setOrientation(HORIZONTAL);
- mProgressBar.setVisibility(VISIBLE);
- break;
-
- case LAYOUT_MODE_COMPACT:
- setOrientation(VERTICAL);
- mProgressBar.setVisibility(GONE);
- ((MarginLayoutParams) mPlayPauseButton.getLayoutParams()).setMargins(0, 0, 0, 0);
- ((MarginLayoutParams) mChronometer.getLayoutParams()).setMargins(0, 0, 0, 0);
- break;
-
- case LAYOUT_MODE_SUB_COMPACT:
- setOrientation(VERTICAL);
- mProgressBar.setVisibility(GONE);
- mChronometer.setVisibility(GONE);
- ((MarginLayoutParams) mPlayPauseButton.getLayoutParams()).setMargins(0, 0, 0, 0);
- final ImageView playButton = (ImageView) findViewById(R.id.play_button);
- playButton.setImageDrawable(
- getResources().getDrawable(R.drawable.ic_preview_play));
- final ImageView pauseButton = (ImageView) findViewById(R.id.pause_button);
- pauseButton.setImageDrawable(
- getResources().getDrawable(R.drawable.ic_preview_pause));
- break;
-
- default:
- Assert.fail("Unsupported mode for AudioAttachmentView!");
- break;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/AudioPlaybackProgressBar.java b/src/com/android/messaging/ui/AudioPlaybackProgressBar.java
deleted file mode 100644
index a5b3a57..0000000
--- a/src/com/android/messaging/ui/AudioPlaybackProgressBar.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * 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;
-
-import android.animation.ObjectAnimator;
-import android.animation.TimeAnimator;
-import android.animation.TimeAnimator.TimeListener;
-import android.content.Context;
-import android.graphics.drawable.ClipDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.SystemClock;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.widget.ProgressBar;
-
-/**
- * Shows a styled progress bar that is synchronized with the playback state of an audio attachment.
- */
-public class AudioPlaybackProgressBar extends ProgressBar implements PlaybackStateView {
- private long mDurationInMillis;
- private final TimeAnimator mUpdateAnimator;
- private long mCumulativeTime = 0;
- private long mCurrentPlayStartTime = 0;
- private boolean mIncoming = false;
-
- public AudioPlaybackProgressBar(final Context context, final AttributeSet attrs) {
- super(context, attrs);
-
- mUpdateAnimator = new TimeAnimator();
- mUpdateAnimator.setRepeatCount(ObjectAnimator.INFINITE);
- mUpdateAnimator.setTimeListener(new TimeListener() {
- @Override
- public void onTimeUpdate(final TimeAnimator animation, final long totalTime,
- final long deltaTime) {
- int progress = 0;
- if (mDurationInMillis > 0) {
- progress = (int) (((mCumulativeTime + SystemClock.elapsedRealtime() -
- mCurrentPlayStartTime) * 1.0f / mDurationInMillis) * 100);
- progress = Math.max(Math.min(progress, 100), 0);
- }
- setProgress(progress);
- }
- });
- updateAppearance();
- }
-
- /**
- * Sets the duration of the audio that's being played, in milliseconds.
- */
- public void setDuration(final long durationInMillis) {
- mDurationInMillis = durationInMillis;
- }
-
- @Override
- public void restart() {
- reset();
- resume();
- }
-
- @Override
- public void reset() {
- stopUpdateTicks();
- setProgress(0);
- mCumulativeTime = 0;
- mCurrentPlayStartTime = 0;
- }
-
- @Override
- public void resume() {
- mCurrentPlayStartTime = SystemClock.elapsedRealtime();
- startUpdateTicks();
- }
-
- @Override
- public void pause() {
- mCumulativeTime += SystemClock.elapsedRealtime() - mCurrentPlayStartTime;
- stopUpdateTicks();
- }
-
- private void startUpdateTicks() {
- if (!mUpdateAnimator.isStarted()) {
- mUpdateAnimator.start();
- }
- }
-
- private void stopUpdateTicks() {
- if (mUpdateAnimator.isStarted()) {
- mUpdateAnimator.end();
- }
- }
-
- private void updateAppearance() {
- final Drawable drawable =
- ConversationDrawables.get().getAudioProgressDrawable(mIncoming);
- final ClipDrawable clipDrawable = new ClipDrawable(drawable, Gravity.START,
- ClipDrawable.HORIZONTAL);
- setProgressDrawable(clipDrawable);
- setBackground(ConversationDrawables.get()
- .getAudioProgressBackgroundDrawable(mIncoming));
- }
-
- public void setVisualStyle(final boolean incoming) {
- if (mIncoming != incoming) {
- mIncoming = incoming;
- updateAppearance();
- }
- }
-}
diff --git a/src/com/android/messaging/ui/BaseBugleActivity.java b/src/com/android/messaging/ui/BaseBugleActivity.java
deleted file mode 100644
index 1236282..0000000
--- a/src/com/android/messaging/ui/BaseBugleActivity.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-import com.android.messaging.util.BugleActivityUtil;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.UiUtils;
-
-/**
- * Base class for app activities that would normally derive from Activity. Responsible for
- * ensuring app requirements are met during onResume()
- */
-public class BaseBugleActivity extends Activity {
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (UiUtils.redirectToPermissionCheckIfNeeded(this)) {
- return;
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onResume");
- BugleActivityUtil.onActivityResume(this, BaseBugleActivity.this);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onPause");
- }
-}
diff --git a/src/com/android/messaging/ui/BaseBugleFragmentActivity.java b/src/com/android/messaging/ui/BaseBugleFragmentActivity.java
deleted file mode 100644
index 947970f..0000000
--- a/src/com/android/messaging/ui/BaseBugleFragmentActivity.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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;
-
-import android.app.Activity;
-
-import com.android.messaging.util.BugleActivityUtil;
-import com.android.messaging.util.LogUtil;
-
-/**
- * Base class for app activities that would normally derive from FragmentActivity. Responsible for
- * ensuring app requirements are met during onResume()
- */
-public class BaseBugleFragmentActivity extends Activity {
- @Override
- protected void onResume() {
- super.onResume();
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onResume");
- // Ensure we have a sufficient version of Google Play Services, prompting for upgrade and
- // disabling the data updates if we don't have the correct version.
- BugleActivityUtil.onActivityResume(this, BaseBugleFragmentActivity.this);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onPause");
- }
-}
diff --git a/src/com/android/messaging/ui/BasePagerViewHolder.java b/src/com/android/messaging/ui/BasePagerViewHolder.java
deleted file mode 100644
index a82ecce..0000000
--- a/src/com/android/messaging/ui/BasePagerViewHolder.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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;
-
-import android.os.Parcelable;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * The base pager view holder implementation that handles basic view creation/destruction logic,
- * as well as logic to save/restore instance state that's persisted not only for activity
- * reconstruction (e.g. during a configuration change), but also cases where the individual
- * page view gets destroyed and recreated.
- *
- * To opt into saving/restoring instance state behavior for a particular page view, let the
- * PageView implement PersistentInstanceState to save and restore instance states to/from a
- * Parcelable.
- */
-public abstract class BasePagerViewHolder implements PagerViewHolder {
- protected View mView;
- protected Parcelable mSavedState;
-
- /**
- * This is called when the entire view pager is being torn down (due to configuration change
- * for example) that will be restored later.
- */
- @Override
- public Parcelable saveState() {
- savePendingState();
- return mSavedState;
- }
-
- /**
- * This is called when the view pager is being restored.
- */
- @Override
- public void restoreState(final Parcelable restoredState) {
- if (restoredState != null) {
- mSavedState = restoredState;
- // If the view is already there, let it restore the state. Otherwise, it will be
- // restored the next time the view gets created.
- restorePendingState();
- }
- }
-
- @Override
- public void resetState() {
- mSavedState = null;
- if (mView != null && (mView instanceof PersistentInstanceState)) {
- ((PersistentInstanceState) mView).resetState();
- }
- }
-
- /**
- * This is called when the view itself is being torn down. This may happen when the user
- * has flipped to another page in the view pager, so we want to save the current state if
- * possible.
- */
- @Override
- public View destroyView() {
- savePendingState();
- final View retView = mView;
- mView = null;
- return retView;
- }
-
- @Override
- public View getView(ViewGroup container) {
- if (mView == null) {
- mView = createView(container);
- // When initially created, check if the view has any saved state. If so restore it.
- restorePendingState();
- }
- return mView;
- }
-
- private void savePendingState() {
- if (mView != null && (mView instanceof PersistentInstanceState)) {
- mSavedState = ((PersistentInstanceState) mView).saveState();
- }
- }
-
- private void restorePendingState() {
- if (mView != null && (mView instanceof PersistentInstanceState) && (mSavedState != null)) {
- ((PersistentInstanceState) mView).restoreState(mSavedState);
- }
- }
-
- /**
- * Create and initialize a new page view.
- */
- protected abstract View createView(ViewGroup container);
-}
diff --git a/src/com/android/messaging/ui/BlockedParticipantListItemView.java b/src/com/android/messaging/ui/BlockedParticipantListItemView.java
deleted file mode 100644
index 2ccdebf..0000000
--- a/src/com/android/messaging/ui/BlockedParticipantListItemView.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.support.v4.text.BidiFormatter;
-import android.support.v4.text.TextDirectionHeuristicsCompat;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ParticipantListItemData;
-
-/**
- * View for individual participant in blocked participants list.
- *
- * Unblocks participant when clicked.
- */
-public class BlockedParticipantListItemView extends LinearLayout {
- private TextView mNameTextView;
- private ContactIconView mContactIconView;
- private ParticipantListItemData mData;
-
- public BlockedParticipantListItemView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- mNameTextView = (TextView) findViewById(R.id.name);
- mContactIconView = (ContactIconView) findViewById(R.id.contact_icon);
- setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View v) {
- mData.unblock(getContext());
- }
- });
- }
-
- public void bind(final ParticipantListItemData data) {
- mData = data;
- final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
- mNameTextView.setText(bidiFormatter.unicodeWrap(
- data.getDisplayName(), TextDirectionHeuristicsCompat.LTR));
- mContactIconView.setImageResourceUri(data.getAvatarUri(), data.getContactId(),
- data.getLookupKey(), data.getNormalizedDestination());
- mNameTextView.setText(data.getDisplayName());
- }
-}
diff --git a/src/com/android/messaging/ui/BlockedParticipantsActivity.java b/src/com/android/messaging/ui/BlockedParticipantsActivity.java
deleted file mode 100644
index b740264..0000000
--- a/src/com/android/messaging/ui/BlockedParticipantsActivity.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import android.view.MenuItem;
-
-import com.android.messaging.R;
-import com.android.messaging.util.Assert;
-
-/**
- * Show a list of currently blocked participants.
- */
-public class BlockedParticipantsActivity extends BugleActionBarActivity {
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.blocked_participants_activity);
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- }
-
- @Override
- public void onAttachFragment(final Fragment fragment) {
- Assert.isTrue(fragment instanceof BlockedParticipantsFragment);
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- // Treat the home press as back press so that when we go back to
- // ConversationActivity, it doesn't lose its original intent (conversation id etc.)
- onBackPressed();
- return true;
-
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/BlockedParticipantsFragment.java b/src/com/android/messaging/ui/BlockedParticipantsFragment.java
deleted file mode 100644
index 3ab54ab..0000000
--- a/src/com/android/messaging/ui/BlockedParticipantsFragment.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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;
-
-import android.app.Fragment;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.support.v4.widget.CursorAdapter;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ListView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.BlockedParticipantsData;
-import com.android.messaging.datamodel.data.BlockedParticipantsData.BlockedParticipantsDataListener;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.util.Assert;
-
-/**
- * Show a list of currently blocked participants.
- */
-public class BlockedParticipantsFragment extends Fragment
- implements BlockedParticipantsDataListener {
- private ListView mListView;
- private BlockedParticipantListAdapter mAdapter;
- private final Binding<BlockedParticipantsData> mBinding =
- BindingBase.createBinding(this);
-
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedInstanceState) {
- final View view =
- inflater.inflate(R.layout.blocked_participants_fragment, container, false);
- mListView = (ListView) view.findViewById(android.R.id.list);
- mAdapter = new BlockedParticipantListAdapter(getActivity(), null);
- mListView.setAdapter(mAdapter);
- mBinding.bind(DataModel.get().createBlockedParticipantsData(getActivity(), this));
- mBinding.getData().init(getLoaderManager(), mBinding);
- return view;
- }
-
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mBinding.unbind();
- }
-
- /**
- * An adapter to display ParticipantListItemView based on ParticipantData.
- */
- private class BlockedParticipantListAdapter extends CursorAdapter {
- public BlockedParticipantListAdapter(final Context context, final Cursor cursor) {
- super(context, cursor, 0);
- }
-
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- return LayoutInflater.from(context)
- .inflate(R.layout.blocked_participant_list_item_view, parent, false);
- }
-
- @Override
- public void bindView(View view, Context context, Cursor cursor) {
- Assert.isTrue(view instanceof BlockedParticipantListItemView);
- ((BlockedParticipantListItemView) view).bind(
- mBinding.getData().createParticipantListItemData(cursor));
- }
- }
-
- @Override
- public void onBlockedParticipantsCursorUpdated(Cursor cursor) {
- mAdapter.swapCursor(cursor);
- }
-}
diff --git a/src/com/android/messaging/ui/BugleActionBarActivity.java b/src/com/android/messaging/ui/BugleActionBarActivity.java
deleted file mode 100644
index 80dabb8..0000000
--- a/src/com/android/messaging/ui/BugleActionBarActivity.java
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- * 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;
-
-import android.graphics.drawable.ColorDrawable;
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.support.v7.app.ActionBarActivity;
-import android.view.ActionMode;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-
-import com.android.messaging.R;
-import com.android.messaging.util.BugleActivityUtil;
-import com.android.messaging.util.ImeUtil;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.UiUtils;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Base class for app activities that use an action bar. Responsible for logging/telemetry/other
- * needs that will be common for all activities. We can break out the common code if/when we need
- * a version that doesn't use an actionbar.
- */
-public class BugleActionBarActivity extends ActionBarActivity implements ImeUtil.ImeStateHost {
- // Tracks the list of observers opting in for IME state change.
- private final Set<ImeUtil.ImeStateObserver> mImeStateObservers = new HashSet<>();
-
- // Tracks the soft keyboard display state
- private boolean mImeOpen;
-
- // The ActionMode that represents the modal contextual action bar, using our own implementation
- // rather than the built in contextual action bar to reduce jank
- private CustomActionMode mActionMode;
-
- // The menu for the action bar
- private Menu mActionBarMenu;
-
- // Used to determine if a onDisplayHeightChanged was due to the IME opening or rotation of the
- // device
- private int mLastScreenHeight;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (UiUtils.redirectToPermissionCheckIfNeeded(this)) {
- return;
- }
-
- mLastScreenHeight = getResources().getDisplayMetrics().heightPixels;
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onCreate");
- }
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onStart");
- }
- }
-
- @Override
- protected void onRestart() {
- super.onStop();
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onRestart");
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onResume");
- }
- BugleActivityUtil.onActivityResume(this, BugleActionBarActivity.this);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onPause");
- }
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onStop");
- }
- }
-
- private boolean mDestroyed;
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mDestroyed = true;
- }
-
- public boolean getIsDestroyed() {
- return mDestroyed;
- }
-
- @Override
- public void onDisplayHeightChanged(final int heightSpecification) {
- int screenHeight = getResources().getDisplayMetrics().heightPixels;
-
- if (screenHeight != mLastScreenHeight) {
- // Appears to be an orientation change, don't fire ime updates
- mLastScreenHeight = screenHeight;
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onDisplayHeightChanged " +
- " screenHeight: " + screenHeight + " lastScreenHeight: " + mLastScreenHeight +
- " Skipped, appears to be orientation change.");
- return;
- }
- final ActionBar actionBar = getSupportActionBar();
- if (actionBar != null && actionBar.isShowing()) {
- screenHeight -= actionBar.getHeight();
- }
- final int height = View.MeasureSpec.getSize(heightSpecification);
-
- final boolean imeWasOpen = mImeOpen;
- mImeOpen = screenHeight - height > 100;
-
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_TAG, this.getLocalClassName() + ".onDisplayHeightChanged " +
- "imeWasOpen: " + imeWasOpen + " mImeOpen: " + mImeOpen + " screenHeight: " +
- screenHeight + " height: " + height);
- }
-
- if (imeWasOpen != mImeOpen) {
- for (final ImeUtil.ImeStateObserver observer : mImeStateObservers) {
- observer.onImeStateChanged(mImeOpen);
- }
- }
- }
-
- @Override
- public void registerImeStateObserver(final ImeUtil.ImeStateObserver observer) {
- mImeStateObservers.add(observer);
- }
-
- @Override
- public void unregisterImeStateObserver(final ImeUtil.ImeStateObserver observer) {
- mImeStateObservers.remove(observer);
- }
-
- @Override
- public boolean isImeOpen() {
- return mImeOpen;
- }
-
- @Override
- public boolean onCreateOptionsMenu(final Menu menu) {
- mActionBarMenu = menu;
- if (mActionMode != null &&
- mActionMode.getCallback().onCreateActionMode(mActionMode, menu)) {
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(final Menu menu) {
- mActionBarMenu = menu;
- if (mActionMode != null &&
- mActionMode.getCallback().onPrepareActionMode(mActionMode, menu)) {
- return true;
- }
- return super.onPrepareOptionsMenu(menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem menuItem) {
- if (mActionMode != null &&
- mActionMode.getCallback().onActionItemClicked(mActionMode, menuItem)) {
- return true;
- }
-
- switch (menuItem.getItemId()) {
- case android.R.id.home:
- if (mActionMode != null) {
- dismissActionMode();
- return true;
- }
- }
- return super.onOptionsItemSelected(menuItem);
- }
-
- @Override
- public ActionMode startActionMode(final ActionMode.Callback callback) {
- mActionMode = new CustomActionMode(callback);
- supportInvalidateOptionsMenu();
- invalidateActionBar();
- return mActionMode;
- }
-
- public void dismissActionMode() {
- if (mActionMode != null) {
- mActionMode.finish();
- mActionMode = null;
- invalidateActionBar();
- }
- }
-
- public ActionMode getActionMode() {
- return mActionMode;
- }
-
- protected ActionMode.Callback getActionModeCallback() {
- if (mActionMode == null) {
- return null;
- }
-
- return mActionMode.getCallback();
- }
-
- /**
- * Receives and handles action bar invalidation request from sub-components of this activity.
- *
- * <p>Normally actions have sole control over the action bar, but in order to support seamless
- * transitions for components such as the full screen media picker, we have to let it take over
- * the action bar and then restore its state afterwards</p>
- *
- * <p>If a fragment does anything that may change the action bar, it should call this method
- * and then it is this method's responsibility to figure out which component "controls" the
- * action bar and delegate the updating of the action bar to that component</p>
- */
- public final void invalidateActionBar() {
- if (mActionMode != null) {
- mActionMode.updateActionBar(getSupportActionBar());
- } else {
- updateActionBar(getSupportActionBar());
- }
- }
-
- protected void updateActionBar(final ActionBar actionBar) {
- actionBar.setHomeAsUpIndicator(null);
- }
-
- /**
- * Custom ActionMode implementation which allows us to just replace the contents of the main
- * action bar rather than overlay over it
- */
- private class CustomActionMode extends ActionMode {
- private CharSequence mTitle;
- private CharSequence mSubtitle;
- private View mCustomView;
- private final Callback mCallback;
-
- public CustomActionMode(final Callback callback) {
- mCallback = callback;
- }
-
- @Override
- public void setTitle(final CharSequence title) {
- mTitle = title;
- }
-
- @Override
- public void setTitle(final int resId) {
- mTitle = getResources().getString(resId);
- }
-
- @Override
- public void setSubtitle(final CharSequence subtitle) {
- mSubtitle = subtitle;
- }
-
- @Override
- public void setSubtitle(final int resId) {
- mSubtitle = getResources().getString(resId);
- }
-
- @Override
- public void setCustomView(final View view) {
- mCustomView = view;
- }
-
- @Override
- public void invalidate() {
- invalidateActionBar();
- }
-
- @Override
- public void finish() {
- mActionMode = null;
- mCallback.onDestroyActionMode(this);
- supportInvalidateOptionsMenu();
- invalidateActionBar();
- }
-
- @Override
- public Menu getMenu() {
- return mActionBarMenu;
- }
-
- @Override
- public CharSequence getTitle() {
- return mTitle;
- }
-
- @Override
- public CharSequence getSubtitle() {
- return mSubtitle;
- }
-
- @Override
- public View getCustomView() {
- return mCustomView;
- }
-
- @Override
- public MenuInflater getMenuInflater() {
- return BugleActionBarActivity.this.getMenuInflater();
- }
-
- public Callback getCallback() {
- return mCallback;
- }
-
- public void updateActionBar(final ActionBar actionBar) {
- actionBar.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP);
- actionBar.setDisplayShowTitleEnabled(false);
- actionBar.setDisplayShowCustomEnabled(false);
- mActionMode.getCallback().onPrepareActionMode(mActionMode, mActionBarMenu);
- actionBar.setBackgroundDrawable(new ColorDrawable(
- getResources().getColor(R.color.contextual_action_bar_background_color)));
- actionBar.setHomeAsUpIndicator(R.drawable.ic_cancel_small_dark);
- actionBar.show();
- }
- }
-}
diff --git a/src/com/android/messaging/ui/BugleAnimationTags.java b/src/com/android/messaging/ui/BugleAnimationTags.java
deleted file mode 100644
index b141f5b..0000000
--- a/src/com/android/messaging/ui/BugleAnimationTags.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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;
-
-
-/**
- * Defines a list of animation tags used as android:transitionName properties used for L's
- * hero transitions.
- */
-public class BugleAnimationTags {
- /**
- * The tag for the FAB in the conversation list activity.
- */
- public static final String TAG_FABICON = "bugle:fabicon";
-
- /**
- * The tag for the content view of a conversation list item view.
- */
- public static final String TAG_CLIVCONTENT = "bugle:clivcontent";
-
- /**
- * The tag for the action bar.
- */
- public static final String TAG_ACTIONBAR = "bugle:actionbar";
-}
diff --git a/src/com/android/messaging/ui/ClassZeroActivity.java b/src/com/android/messaging/ui/ClassZeroActivity.java
deleted file mode 100644
index 129ec19..0000000
--- a/src/com/android/messaging/ui/ClassZeroActivity.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * 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;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.ContentValues;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.SystemClock;
-import android.provider.Telephony.Sms;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.Window;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.action.ReceiveSmsMessageAction;
-import com.android.messaging.util.Assert;
-
-import java.util.ArrayList;
-
-/**
- * Display a class-zero SMS message to the user. Wait for the user to dismiss
- * it.
- */
-public class ClassZeroActivity extends Activity {
- private static final boolean VERBOSE = false;
- private static final String TAG = "display_00";
- private static final int ON_AUTO_SAVE = 1;
-
- /** Default timer to dismiss the dialog. */
- private static final long DEFAULT_TIMER = 5 * 60 * 1000;
-
- /** To remember the exact time when the timer should fire. */
- private static final String TIMER_FIRE = "timer_fire";
-
- private ContentValues mMessageValues = null;
-
- /** Is the message read. */
- private boolean mRead = false;
-
- /** The timer to dismiss the dialog automatically. */
- private long mTimerSet = 0;
- private AlertDialog mDialog = null;
-
- private ArrayList<ContentValues> mMessageQueue = null;
-
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(final Message msg) {
- // Do not handle an invalid message.
- if (msg.what == ON_AUTO_SAVE) {
- mRead = false;
- mDialog.dismiss();
- saveMessage();
- processNextMessage();
- }
- }
- };
-
- private boolean queueMsgFromIntent(final Intent msgIntent) {
- final ContentValues messageValues =
- msgIntent.getParcelableExtra(UIIntents.UI_INTENT_EXTRA_MESSAGE_VALUES);
- // that takes the format argument is a hidden API right now.
- final String message = messageValues.getAsString(Sms.BODY);
- if (TextUtils.isEmpty(message)) {
- if (mMessageQueue.size() == 0) {
- finish();
- }
- return false;
- }
- mMessageQueue.add(messageValues);
- return true;
- }
-
- private void processNextMessage() {
- if (mMessageQueue.size() > 0) {
- mMessageQueue.remove(0);
- }
- if (mMessageQueue.size() == 0) {
- finish();
- } else {
- displayZeroMessage(mMessageQueue.get(0));
- }
- }
-
- private void saveMessage() {
- mMessageValues.put(Sms.Inbox.READ, mRead ? Integer.valueOf(1) : Integer.valueOf(0));
- final ReceiveSmsMessageAction action = new ReceiveSmsMessageAction(mMessageValues);
- action.start();
- }
-
- @Override
- protected void onCreate(final Bundle icicle) {
- super.onCreate(icicle);
- requestWindowFeature(Window.FEATURE_NO_TITLE);
-
- if (mMessageQueue == null) {
- mMessageQueue = new ArrayList<ContentValues>();
- }
- if (!queueMsgFromIntent(getIntent())) {
- return;
- }
- Assert.isTrue(mMessageQueue.size() == 1);
- if (mMessageQueue.size() == 1) {
- displayZeroMessage(mMessageQueue.get(0));
- }
-
- if (icicle != null) {
- mTimerSet = icicle.getLong(TIMER_FIRE, mTimerSet);
- }
- }
-
- private void displayZeroMessage(final ContentValues messageValues) {
- /* This'll be used by the save action */
- mMessageValues = messageValues;
- final String message = messageValues.getAsString(Sms.BODY);;
-
- mDialog = new AlertDialog.Builder(this).setMessage(message)
- .setPositiveButton(R.string.save, mSaveListener)
- .setNegativeButton(android.R.string.cancel, mCancelListener)
- .setTitle(R.string.class_0_message_activity)
- .setCancelable(false).show();
- final long now = SystemClock.uptimeMillis();
- mTimerSet = now + DEFAULT_TIMER;
- }
-
- @Override
- protected void onNewIntent(final Intent msgIntent) {
- // Running with another visible message, queue this one
- queueMsgFromIntent(msgIntent);
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- final long now = SystemClock.uptimeMillis();
- if (mTimerSet <= now) {
- // Save the message if the timer already expired.
- mHandler.sendEmptyMessage(ON_AUTO_SAVE);
- } else {
- mHandler.sendEmptyMessageAtTime(ON_AUTO_SAVE, mTimerSet);
- if (VERBOSE) {
- Log.d(TAG, "onRestart time = " + Long.toString(mTimerSet) + " "
- + this.toString());
- }
- }
- }
-
- @Override
- protected void onSaveInstanceState(final Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putLong(TIMER_FIRE, mTimerSet);
- if (VERBOSE) {
- Log.d(TAG, "onSaveInstanceState time = " + Long.toString(mTimerSet)
- + " " + this.toString());
- }
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- mHandler.removeMessages(ON_AUTO_SAVE);
- if (VERBOSE) {
- Log.d(TAG, "onStop time = " + Long.toString(mTimerSet)
- + " " + this.toString());
- }
- }
-
- private final OnClickListener mCancelListener = new OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int whichButton) {
- dialog.dismiss();
- processNextMessage();
- }
- };
-
- private final OnClickListener mSaveListener = new OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int whichButton) {
- mRead = true;
- saveMessage();
- dialog.dismiss();
- processNextMessage();
- }
- };
-}
diff --git a/src/com/android/messaging/ui/CompositeAdapter.java b/src/com/android/messaging/ui/CompositeAdapter.java
deleted file mode 100644
index 620e511..0000000
--- a/src/com/android/messaging/ui/CompositeAdapter.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.database.DataSetObserver;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-
-/**
- * A general purpose adapter that composes one or more other adapters. It
- * appends them in the order they are added.
- */
-public class CompositeAdapter extends BaseAdapter {
-
- private static final int INITIAL_CAPACITY = 2;
-
- public static class Partition {
- boolean mShowIfEmpty;
- boolean mHasHeader;
- BaseAdapter mAdapter;
-
- public Partition(final boolean showIfEmpty, final boolean hasHeader,
- final BaseAdapter adapter) {
- this.mShowIfEmpty = showIfEmpty;
- this.mHasHeader = hasHeader;
- this.mAdapter = adapter;
- }
-
- /**
- * True if the directory should be shown even if no contacts are found.
- */
- public boolean showIfEmpty() {
- return mShowIfEmpty;
- }
-
- public boolean hasHeader() {
- return mHasHeader;
- }
-
- public int getCount() {
- int count = mAdapter.getCount();
- if (mHasHeader && (count != 0 || mShowIfEmpty)) {
- count++;
- }
- return count;
- }
-
- public BaseAdapter getAdapter() {
- return mAdapter;
- }
-
- public View getHeaderView(final View convertView, final ViewGroup parentView) {
- return null;
- }
-
- public void close() {
- // do nothing in base class.
- }
- }
-
- private class Observer extends DataSetObserver {
- @Override
- public void onChanged() {
- CompositeAdapter.this.notifyDataSetChanged();
- }
-
- @Override
- public void onInvalidated() {
- CompositeAdapter.this.notifyDataSetInvalidated();
- }
- };
-
- protected final Context mContext;
- private Partition[] mPartitions;
- private int mSize = 0;
- private int mCount = 0;
- private boolean mCacheValid = true;
- private final Observer mObserver;
-
- public CompositeAdapter(final Context context) {
- mContext = context;
- mObserver = new Observer();
- mPartitions = new Partition[INITIAL_CAPACITY];
- }
-
- public Context getContext() {
- return mContext;
- }
-
- public void addPartition(final Partition partition) {
- if (mSize >= mPartitions.length) {
- final int newCapacity = mSize + 2;
- final Partition[] newAdapters = new Partition[newCapacity];
- System.arraycopy(mPartitions, 0, newAdapters, 0, mSize);
- mPartitions = newAdapters;
- }
- mPartitions[mSize++] = partition;
- partition.getAdapter().registerDataSetObserver(mObserver);
- invalidate();
- notifyDataSetChanged();
- }
-
- public void removePartition(final int index) {
- final Partition partition = mPartitions[index];
- partition.close();
- System.arraycopy(mPartitions, index + 1, mPartitions, index,
- mSize - index - 1);
- mSize--;
- partition.getAdapter().unregisterDataSetObserver(mObserver);
- invalidate();
- notifyDataSetChanged();
- }
-
- public void clearPartitions() {
- for (int i = 0; i < mSize; i++) {
- final Partition partition = mPartitions[i];
- partition.close();
- partition.getAdapter().unregisterDataSetObserver(mObserver);
- }
- invalidate();
- notifyDataSetChanged();
- }
-
- public Partition getPartition(final int index) {
- return mPartitions[index];
- }
-
- public int getPartitionAtPosition(final int position) {
- ensureCacheValid();
- int start = 0;
- for (int i = 0; i < mSize; i++) {
- final int end = start + mPartitions[i].getCount();
- if (position >= start && position < end) {
- int offset = position - start;
- if (mPartitions[i].hasHeader() &&
- (mPartitions[i].getCount() > 0 || mPartitions[i].showIfEmpty())) {
- offset--;
- }
- if (offset == -1) {
- return -1;
- }
- return i;
- }
- start = end;
- }
- return mSize - 1;
- }
-
- public int getPartitionCount() {
- return mSize;
- }
-
- public void invalidate() {
- mCacheValid = false;
- }
-
- private void ensureCacheValid() {
- if (mCacheValid) {
- return;
- }
- mCount = 0;
- for (int i = 0; i < mSize; i++) {
- mCount += mPartitions[i].getCount();
- }
- }
-
- @Override
- public int getCount() {
- ensureCacheValid();
- return mCount;
- }
-
- public int getCount(final int index) {
- ensureCacheValid();
- return mPartitions[index].getCount();
- }
-
- @Override
- public Object getItem(final int position) {
- ensureCacheValid();
- int start = 0;
- for (int i = 0; i < mSize; i++) {
- final int end = start + mPartitions[i].getCount();
- if (position >= start && position < end) {
- final int offset = position - start;
- final Partition partition = mPartitions[i];
- if (partition.hasHeader() && offset == 0 &&
- (partition.getCount() > 0 || partition.showIfEmpty())) {
- // This is the header
- return null;
- }
- return mPartitions[i].getAdapter().getItem(offset);
- }
- start = end;
- }
-
- return null;
- }
-
- @Override
- public long getItemId(final int position) {
- ensureCacheValid();
- int start = 0;
- for (int i = 0; i < mSize; i++) {
- final int end = start + mPartitions[i].getCount();
- if (position >= start && position < end) {
- final int offset = position - start;
- final Partition partition = mPartitions[i];
- if (partition.hasHeader() && offset == 0 &&
- (partition.getCount() > 0 || partition.showIfEmpty())) {
- // Header
- return 0;
- }
- return mPartitions[i].getAdapter().getItemId(offset);
- }
- start = end;
- }
-
- return 0;
- }
-
- @Override
- public boolean isEnabled(int position) {
- ensureCacheValid();
- int start = 0;
- for (int i = 0; i < mSize; i++) {
- final int end = start + mPartitions[i].getCount();
- if (position >= start && position < end) {
- final int offset = position - start;
- final Partition partition = mPartitions[i];
- if (partition.hasHeader() && offset == 0 &&
- (partition.getCount() > 0 || partition.showIfEmpty())) {
- // This is the header
- return false;
- }
- return true;
- }
- start = end;
- }
- return true;
- }
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parentView) {
- ensureCacheValid();
- int start = 0;
- for (int i = 0; i < mSize; i++) {
- final Partition partition = mPartitions[i];
- final int end = start + partition.getCount();
- if (position >= start && position < end) {
- int offset = position - start;
- View view;
- if (partition.hasHeader() &&
- (partition.getCount() > 0 || partition.showIfEmpty())) {
- offset = offset - 1;
- }
- if (offset == -1) {
- view = partition.getHeaderView(convertView, parentView);
- } else {
- view = partition.getAdapter().getView(offset, convertView, parentView);
- }
- if (view == null) {
- throw new NullPointerException("View should not be null, partition: " + i
- + " position: " + offset);
- }
- return view;
- }
- start = end;
- }
-
- throw new ArrayIndexOutOfBoundsException(position);
- }
-}
diff --git a/src/com/android/messaging/ui/ContactIconView.java b/src/com/android/messaging/ui/ContactIconView.java
deleted file mode 100644
index 44983ab..0000000
--- a/src/com/android/messaging/ui/ContactIconView.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.media.AvatarGroupRequestDescriptor;
-import com.android.messaging.datamodel.media.AvatarRequestDescriptor;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.ContactUtil;
-
-/**
- * A view used to render contact icons. This class derives from AsyncImageView, so it loads contact
- * icons from MediaResourceManager, and it handles more rendering logic than an AsyncImageView
- * (draws a circular bitmap).
- */
-public class ContactIconView extends AsyncImageView {
- private static final int NORMAL_ICON_SIZE_ID = 0;
- private static final int LARGE_ICON_SIZE_ID = 1;
- private static final int SMALL_ICON_SIZE_ID = 2;
-
- protected final int mIconSize;
- private final int mColorPressedId;
-
- private long mContactId;
- private String mContactLookupKey;
- private String mNormalizedDestination;
- private Uri mAvatarUri;
- private boolean mDisableClickHandler;
-
- public ContactIconView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
-
- final Resources resources = context.getResources();
- final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ContactIconView);
-
- final int iconSizeId = a.getInt(R.styleable.ContactIconView_iconSize, 0);
- switch (iconSizeId) {
- case NORMAL_ICON_SIZE_ID:
- mIconSize = (int) resources.getDimension(
- R.dimen.contact_icon_view_normal_size);
- break;
- case LARGE_ICON_SIZE_ID:
- mIconSize = (int) resources.getDimension(
- R.dimen.contact_icon_view_large_size);
- break;
- case SMALL_ICON_SIZE_ID:
- mIconSize = (int) resources.getDimension(
- R.dimen.contact_icon_view_small_size);
- break;
- default:
- // For the compiler, something has to be set even with the assert.
- mIconSize = 0;
- Assert.fail("Unsupported ContactIconView icon size attribute");
- }
- mColorPressedId = resources.getColor(R.color.contact_avatar_pressed_color);
-
- setImage(null);
- a.recycle();
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- setColorFilter(mColorPressedId);
- } else {
- clearColorFilter();
- }
- return super.onTouchEvent(event);
- }
-
- /**
- * Method which allows the automatic hookup of a click handler when the Uri is changed
- */
- public void setImageClickHandlerDisabled(final boolean isHandlerDisabled) {
- mDisableClickHandler = isHandlerDisabled;
- setOnClickListener(null);
- setClickable(false);
- }
-
- /**
- * A convenience method that sets the URI of the contact icon by creating a new image request.
- */
- public void setImageResourceUri(final Uri uri) {
- setImageResourceUri(uri, 0, null, null);
- }
-
- public void setImageResourceUri(final Uri uri, final long contactId,
- final String contactLookupKey, final String normalizedDestination) {
- if (uri == null) {
- setImageResourceId(null);
- } else {
- final String avatarType = AvatarUriUtil.getAvatarType(uri);
- if (AvatarUriUtil.TYPE_GROUP_URI.equals(avatarType)) {
- setImageResourceId(new AvatarGroupRequestDescriptor(uri, mIconSize, mIconSize));
- } else {
- setImageResourceId(new AvatarRequestDescriptor(uri, mIconSize, mIconSize));
- }
- }
-
- mContactId = contactId;
- mContactLookupKey = contactLookupKey;
- mNormalizedDestination = normalizedDestination;
- mAvatarUri = uri;
-
- maybeInitializeOnClickListener();
- }
-
- protected void maybeInitializeOnClickListener() {
- if ((mContactId > ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED
- && !TextUtils.isEmpty(mContactLookupKey)) ||
- !TextUtils.isEmpty(mNormalizedDestination)) {
- if (!mDisableClickHandler) {
- setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View view) {
- ContactUtil.showOrAddContact(view, mContactId, mContactLookupKey,
- mAvatarUri, mNormalizedDestination);
- }
- });
- }
- } else {
- // This should happen when the phone number is not in the user's contacts or it is a
- // group conversation, group conversations don't have contact phone numbers. If this
- // is the case then absorb the click to prevent propagation.
- setOnClickListener(null);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/ConversationDrawables.java b/src/com/android/messaging/ui/ConversationDrawables.java
deleted file mode 100644
index cf858e2..0000000
--- a/src/com/android/messaging/ui/ConversationDrawables.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.util.ImageUtils;
-
-/**
- * A singleton cache that holds tinted drawable resources for displaying messages, such as
- * message bubbles, audio attachments etc.
- */
-public class ConversationDrawables {
- private static ConversationDrawables sInstance;
-
- // Cache the color filtered bubble drawables so that we don't need to create a
- // new one for each ConversationMessageView.
- private Drawable mIncomingBubbleDrawable;
- private Drawable mOutgoingBubbleDrawable;
- private Drawable mIncomingErrorBubbleDrawable;
- private Drawable mIncomingBubbleNoArrowDrawable;
- private Drawable mOutgoingBubbleNoArrowDrawable;
- private Drawable mAudioPlayButtonDrawable;
- private Drawable mAudioPauseButtonDrawable;
- private Drawable mIncomingAudioProgressBackgroundDrawable;
- private Drawable mOutgoingAudioProgressBackgroundDrawable;
- private Drawable mAudioProgressForegroundDrawable;
- private Drawable mFastScrollThumbDrawable;
- private Drawable mFastScrollThumbPressedDrawable;
- private Drawable mFastScrollPreviewDrawableLeft;
- private Drawable mFastScrollPreviewDrawableRight;
- private final Context mContext;
- private int mOutgoingBubbleColor;
- private int mIncomingErrorBubbleColor;
- private int mIncomingAudioButtonColor;
- private int mSelectedBubbleColor;
- private int mThemeColor;
-
- public static ConversationDrawables get() {
- if (sInstance == null) {
- sInstance = new ConversationDrawables(Factory.get().getApplicationContext());
- }
- return sInstance;
- }
-
- private ConversationDrawables(final Context context) {
- mContext = context;
- // Pre-create all the drawables.
- updateDrawables();
- }
-
- public int getConversationThemeColor() {
- return mThemeColor;
- }
-
- public void updateDrawables() {
- final Resources resources = mContext.getResources();
-
- mIncomingBubbleDrawable = resources.getDrawable(R.drawable.msg_bubble_incoming);
- mIncomingBubbleNoArrowDrawable =
- resources.getDrawable(R.drawable.message_bubble_incoming_no_arrow);
- mIncomingErrorBubbleDrawable = resources.getDrawable(R.drawable.msg_bubble_error);
- mOutgoingBubbleDrawable = resources.getDrawable(R.drawable.msg_bubble_outgoing);
- mOutgoingBubbleNoArrowDrawable =
- resources.getDrawable(R.drawable.message_bubble_outgoing_no_arrow);
- mAudioPlayButtonDrawable = resources.getDrawable(R.drawable.ic_audio_play);
- mAudioPauseButtonDrawable = resources.getDrawable(R.drawable.ic_audio_pause);
- mIncomingAudioProgressBackgroundDrawable =
- resources.getDrawable(R.drawable.audio_progress_bar_background_incoming);
- mOutgoingAudioProgressBackgroundDrawable =
- resources.getDrawable(R.drawable.audio_progress_bar_background_outgoing);
- mAudioProgressForegroundDrawable =
- resources.getDrawable(R.drawable.audio_progress_bar_progress);
- mFastScrollThumbDrawable = resources.getDrawable(R.drawable.fastscroll_thumb);
- mFastScrollThumbPressedDrawable =
- resources.getDrawable(R.drawable.fastscroll_thumb_pressed);
- mFastScrollPreviewDrawableLeft =
- resources.getDrawable(R.drawable.fastscroll_preview_left);
- mFastScrollPreviewDrawableRight =
- resources.getDrawable(R.drawable.fastscroll_preview_right);
- mOutgoingBubbleColor = resources.getColor(R.color.message_bubble_color_outgoing);
- mIncomingErrorBubbleColor =
- resources.getColor(R.color.message_error_bubble_color_incoming);
- mIncomingAudioButtonColor =
- resources.getColor(R.color.message_audio_button_color_incoming);
- mSelectedBubbleColor = resources.getColor(R.color.message_bubble_color_selected);
- mThemeColor = resources.getColor(R.color.primary_color);
- }
-
- public Drawable getBubbleDrawable(final boolean selected, final boolean incoming,
- final boolean needArrow, final boolean isError) {
- final Drawable protoDrawable;
- if (needArrow) {
- if (incoming) {
- protoDrawable = isError && !selected ?
- mIncomingErrorBubbleDrawable : mIncomingBubbleDrawable;
- } else {
- protoDrawable = mOutgoingBubbleDrawable;
- }
- } else if (incoming) {
- protoDrawable = mIncomingBubbleNoArrowDrawable;
- } else {
- protoDrawable = mOutgoingBubbleNoArrowDrawable;
- }
-
- int color;
- if (selected) {
- color = mSelectedBubbleColor;
- } else if (incoming) {
- if (isError) {
- color = mIncomingErrorBubbleColor;
- } else {
- color = mThemeColor;
- }
- } else {
- color = mOutgoingBubbleColor;
- }
-
- return ImageUtils.getTintedDrawable(mContext, protoDrawable, color);
- }
-
- private int getAudioButtonColor(final boolean incoming) {
- return incoming ? mIncomingAudioButtonColor : mThemeColor;
- }
-
- public Drawable getPlayButtonDrawable(final boolean incoming) {
- return ImageUtils.getTintedDrawable(
- mContext, mAudioPlayButtonDrawable, getAudioButtonColor(incoming));
- }
-
- public Drawable getPauseButtonDrawable(final boolean incoming) {
- return ImageUtils.getTintedDrawable(
- mContext, mAudioPauseButtonDrawable, getAudioButtonColor(incoming));
- }
-
- public Drawable getAudioProgressDrawable(final boolean incoming) {
- return ImageUtils.getTintedDrawable(
- mContext, mAudioProgressForegroundDrawable, getAudioButtonColor(incoming));
- }
-
- public Drawable getAudioProgressBackgroundDrawable(final boolean incoming) {
- return incoming ? mIncomingAudioProgressBackgroundDrawable :
- mOutgoingAudioProgressBackgroundDrawable;
- }
-
- public Drawable getFastScrollThumbDrawable(final boolean pressed) {
- if (pressed) {
- return ImageUtils.getTintedDrawable(mContext, mFastScrollThumbPressedDrawable,
- mThemeColor);
- } else {
- return mFastScrollThumbDrawable;
- }
- }
-
- public Drawable getFastScrollPreviewDrawable(boolean positionRight) {
- Drawable protoDrawable = positionRight ? mFastScrollPreviewDrawableRight :
- mFastScrollPreviewDrawableLeft;
- return ImageUtils.getTintedDrawable(mContext, protoDrawable, mThemeColor);
- }
-}
diff --git a/src/com/android/messaging/ui/CursorRecyclerAdapter.java b/src/com/android/messaging/ui/CursorRecyclerAdapter.java
deleted file mode 100644
index f1a7b7d..0000000
--- a/src/com/android/messaging/ui/CursorRecyclerAdapter.java
+++ /dev/null
@@ -1,333 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.database.DataSetObserver;
-import android.os.Handler;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.ViewGroup;
-import android.widget.FilterQueryProvider;
-
-/**
- * Copy of CursorAdapter suited for RecyclerView.
- *
- * TODO: BUG 16327984. Replace this with a framework supported CursorAdapter for
- * RecyclerView when one is available.
- */
-public abstract class CursorRecyclerAdapter<VH extends RecyclerView.ViewHolder>
- extends RecyclerView.Adapter<VH> {
- /**
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected boolean mDataValid;
- /**
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected boolean mAutoRequery;
- /**
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected Cursor mCursor;
- /**
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected Context mContext;
- /**
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected int mRowIDColumn;
- /**
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected ChangeObserver mChangeObserver;
- /**
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected DataSetObserver mDataSetObserver;
- /**
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected FilterQueryProvider mFilterQueryProvider;
-
- /**
- * If set the adapter will call requery() on the cursor whenever a content change
- * notification is delivered. Implies {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
- *
- * @deprecated This option is discouraged, as it results in Cursor queries
- * being performed on the application's UI thread and thus can cause poor
- * responsiveness or even Application Not Responding errors. As an alternative,
- * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
- */
- @Deprecated
- public static final int FLAG_AUTO_REQUERY = 0x01;
-
- /**
- * If set the adapter will register a content observer on the cursor and will call
- * {@link #onContentChanged()} when a notification comes in. Be careful when
- * using this flag: you will need to unset the current Cursor from the adapter
- * to avoid leaks due to its registered observers. This flag is not needed
- * when using a CursorAdapter with a
- * {@link android.content.CursorLoader}.
- */
- public static final int FLAG_REGISTER_CONTENT_OBSERVER = 0x02;
-
- /**
- * Recommended constructor.
- *
- * @param c The cursor from which to get the data.
- * @param context The context
- * @param flags Flags used to determine the behavior of the adapter; may
- * be any combination of {@link #FLAG_AUTO_REQUERY} and
- * {@link #FLAG_REGISTER_CONTENT_OBSERVER}.
- */
- public CursorRecyclerAdapter(final Context context, final Cursor c, final int flags) {
- init(context, c, flags);
- }
-
- void init(final Context context, final Cursor c, int flags) {
- if ((flags & FLAG_AUTO_REQUERY) == FLAG_AUTO_REQUERY) {
- flags |= FLAG_REGISTER_CONTENT_OBSERVER;
- mAutoRequery = true;
- } else {
- mAutoRequery = false;
- }
- final boolean cursorPresent = c != null;
- mCursor = c;
- mDataValid = cursorPresent;
- mContext = context;
- mRowIDColumn = cursorPresent ? c.getColumnIndexOrThrow("_id") : -1;
- if ((flags & FLAG_REGISTER_CONTENT_OBSERVER) == FLAG_REGISTER_CONTENT_OBSERVER) {
- mChangeObserver = new ChangeObserver();
- mDataSetObserver = new MyDataSetObserver();
- } else {
- mChangeObserver = null;
- mDataSetObserver = null;
- }
-
- if (cursorPresent) {
- if (mChangeObserver != null) {
- c.registerContentObserver(mChangeObserver);
- }
- if (mDataSetObserver != null) {
- c.registerDataSetObserver(mDataSetObserver);
- }
- }
- }
-
- /**
- * Returns the cursor.
- * @return the cursor.
- */
- public Cursor getCursor() {
- return mCursor;
- }
-
- @Override
- public int getItemCount() {
- if (mDataValid && mCursor != null) {
- return mCursor.getCount();
- } else {
- return 0;
- }
- }
-
- /**
- * @see android.support.v7.widget.RecyclerView.Adapter#getItem(int)
- */
- public Object getItem(final int position) {
- if (mDataValid && mCursor != null) {
- mCursor.moveToPosition(position);
- return mCursor;
- } else {
- return null;
- }
- }
-
- /**
- * @see android.support.v7.widget.RecyclerView.Adapter#getItemId(int)
- */
- @Override
- public long getItemId(final int position) {
- if (mDataValid && mCursor != null) {
- if (mCursor.moveToPosition(position)) {
- return mCursor.getLong(mRowIDColumn);
- } else {
- return 0;
- }
- } else {
- return 0;
- }
- }
-
- @Override
- public VH onCreateViewHolder(final ViewGroup parent, final int viewType) {
- return createViewHolder(mContext, parent, viewType);
- }
-
- @Override
- public void onBindViewHolder(final VH holder, final int position) {
- if (!mDataValid) {
- throw new IllegalStateException("this should only be called when the cursor is valid");
- }
- if (!mCursor.moveToPosition(position)) {
- throw new IllegalStateException("couldn't move cursor to position " + position);
- }
- bindViewHolder(holder, mContext, mCursor);
- }
- /**
- * Bind an existing view to the data pointed to by cursor
- * @param view Existing view, returned earlier by newView
- * @param context Interface to application's global information
- * @param cursor The cursor from which to get the data. The cursor is already
- * moved to the correct position.
- */
- public abstract void bindViewHolder(VH holder, Context context, Cursor cursor);
-
- /**
- * @see android.support.v7.widget.RecyclerView.Adapter#createViewHolder(Context, ViewGroup, int)
- */
- public abstract VH createViewHolder(Context context, ViewGroup parent, int viewType);
-
- /**
- * Change the underlying cursor to a new cursor. If there is an existing cursor it will be
- * closed.
- *
- * @param cursor The new cursor to be used
- */
- public void changeCursor(final Cursor cursor) {
- final Cursor old = swapCursor(cursor);
- if (old != null) {
- old.close();
- }
- }
-
- /**
- * Swap in a new Cursor, returning the old Cursor. Unlike
- * {@link #changeCursor(Cursor)}, the returned old Cursor is <em>not</em>
- * closed.
- *
- * @param newCursor The new cursor to be used.
- * @return Returns the previously set Cursor, or null if there wasa not one.
- * If the given new Cursor is the same instance is the previously set
- * Cursor, null is also returned.
- */
- public Cursor swapCursor(final Cursor newCursor) {
- if (newCursor == mCursor) {
- return null;
- }
- final Cursor oldCursor = mCursor;
- if (oldCursor != null) {
- if (mChangeObserver != null) {
- oldCursor.unregisterContentObserver(mChangeObserver);
- }
- if (mDataSetObserver != null) {
- oldCursor.unregisterDataSetObserver(mDataSetObserver);
- }
- }
- mCursor = newCursor;
- if (newCursor != null) {
- if (mChangeObserver != null) {
- newCursor.registerContentObserver(mChangeObserver);
- }
- if (mDataSetObserver != null) {
- newCursor.registerDataSetObserver(mDataSetObserver);
- }
- mRowIDColumn = newCursor.getColumnIndexOrThrow("_id");
- mDataValid = true;
- // notify the observers about the new cursor
- notifyDataSetChanged();
- } else {
- mRowIDColumn = -1;
- mDataValid = false;
- // notify the observers about the lack of a data set
- notifyDataSetChanged();
- }
- return oldCursor;
- }
-
- /**
- * <p>Converts the cursor into a CharSequence. Subclasses should override this
- * method to convert their results. The default implementation returns an
- * empty String for null values or the default String representation of
- * the value.</p>
- *
- * @param cursor the cursor to convert to a CharSequence
- * @return a CharSequence representing the value
- */
- public CharSequence convertToString(final Cursor cursor) {
- return cursor == null ? "" : cursor.toString();
- }
-
- /**
- * Called when the {@link ContentObserver} on the cursor receives a change notification.
- * The default implementation provides the auto-requery logic, but may be overridden by
- * sub classes.
- *
- * @see ContentObserver#onChange(boolean)
- */
- protected void onContentChanged() {
- if (mAutoRequery && mCursor != null && !mCursor.isClosed()) {
- if (false) {
- Log.v("Cursor", "Auto requerying " + mCursor + " due to update");
- }
- mDataValid = mCursor.requery();
- }
- }
-
- private class ChangeObserver extends ContentObserver {
- public ChangeObserver() {
- super(new Handler());
- }
-
- @Override
- public boolean deliverSelfNotifications() {
- return true;
- }
-
- @Override
- public void onChange(final boolean selfChange) {
- onContentChanged();
- }
- }
-
- private class MyDataSetObserver extends DataSetObserver {
- @Override
- public void onChanged() {
- mDataValid = true;
- notifyDataSetChanged();
- }
-
- @Override
- public void onInvalidated() {
- mDataValid = false;
- notifyDataSetChanged();
- }
- }
-
-}
diff --git a/src/com/android/messaging/ui/CustomHeaderPagerListViewHolder.java b/src/com/android/messaging/ui/CustomHeaderPagerListViewHolder.java
deleted file mode 100644
index 1268c53..0000000
--- a/src/com/android/messaging/ui/CustomHeaderPagerListViewHolder.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AbsListView;
-import android.widget.AbsListView.OnScrollListener;
-import android.widget.CursorAdapter;
-import android.widget.ListView;
-
-import com.android.messaging.util.ImeUtil;
-
-/**
- * Produces and holds a list view and its tab header to be displayed in a ViewPager.
- */
-public abstract class CustomHeaderPagerListViewHolder extends BasePagerViewHolder
- implements CustomHeaderPagerViewHolder {
- private final Context mContext;
- private final CursorAdapter mListAdapter;
- private boolean mListCursorInitialized;
- private ListView mListView;
-
- public CustomHeaderPagerListViewHolder(final Context context,
- final CursorAdapter adapter) {
- mContext = context;
- mListAdapter = adapter;
- }
-
- @Override
- protected View createView(ViewGroup container) {
- final LayoutInflater inflater = (LayoutInflater)
- mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- final View view = inflater.inflate(
- getLayoutResId(),
- null /* root */,
- false /* attachToRoot */);
- final ListView listView = (ListView) view.findViewById(getListViewResId());
- listView.setAdapter(mListAdapter);
- listView.setOnScrollListener(new OnScrollListener() {
- @Override
- public void onScrollStateChanged(final AbsListView view, final int scrollState) {
- if (scrollState != SCROLL_STATE_IDLE) {
- ImeUtil.get().hideImeKeyboard(mContext, view);
- }
- }
-
- @Override
- public void onScroll(final AbsListView view, final int firstVisibleItem,
- final int visibleItemCount, final int totalItemCount) {
- }
- });
- mListView = listView;
- maybeSetEmptyView();
- return view;
- }
-
- public void onContactsCursorUpdated(final Cursor data) {
- mListAdapter.swapCursor(data);
- if (!mListCursorInitialized) {
- // We set the emptyView here instead of in create so that the initial load won't show
- // the empty UI - the system handles this and doesn't do what we would like.
- mListCursorInitialized = true;
- maybeSetEmptyView();
- }
- }
-
- /**
- * We don't want to show the empty view hint until BOTH conditions are met:
- * 1. The view has been created.
- * 2. Cursor data has been loaded once.
- * Due to timing when data is loaded, the view may not be ready (and vice versa). So we
- * are calling this method from both onContactsCursorUpdated & createView.
- */
- private void maybeSetEmptyView() {
- if (mView != null && mListCursorInitialized) {
- final ListEmptyView emptyView = (ListEmptyView) mView.findViewById(getEmptyViewResId());
- if (emptyView != null) {
- emptyView.setTextHint(getEmptyViewTitleResId());
- emptyView.setImageHint(getEmptyViewImageResId());
- final ListView listView = (ListView) mView.findViewById(getListViewResId());
- listView.setEmptyView(emptyView);
- }
- }
- }
-
- public void invalidateList() {
- mListAdapter.notifyDataSetChanged();
- }
-
- /**
- * In order for scene transition to work, we toggle the visibility for each individual list
- * view items so that they can be properly tracked by the scene transition manager.
- * @param show whether the pending transition is to show or hide the list.
- */
- public void toggleVisibilityForPendingTransition(final boolean show, final View epicenterView) {
- if (mListView == null) {
- return;
- }
- final int childCount = mListView.getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View childView = mListView.getChildAt(i);
- if (childView != epicenterView) {
- childView.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
- }
- }
- }
-
- @Override
- public CharSequence getPageTitle(Context context) {
- return context.getString(getPageTitleResId());
- }
-
- protected abstract int getLayoutResId();
- protected abstract int getPageTitleResId();
- protected abstract int getEmptyViewResId();
- protected abstract int getEmptyViewTitleResId();
- protected abstract int getEmptyViewImageResId();
- protected abstract int getListViewResId();
-}
diff --git a/src/com/android/messaging/ui/CustomHeaderPagerViewHolder.java b/src/com/android/messaging/ui/CustomHeaderPagerViewHolder.java
deleted file mode 100644
index 43802cd..0000000
--- a/src/com/android/messaging/ui/CustomHeaderPagerViewHolder.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-
-/**
- * An extension on the standard PagerViewHolder to return a custom header view to be used by
- * CustomHeaderViewPager
- */
-public interface CustomHeaderPagerViewHolder extends PagerViewHolder {
- CharSequence getPageTitle(Context context);
-}
diff --git a/src/com/android/messaging/ui/CustomHeaderViewPager.java b/src/com/android/messaging/ui/CustomHeaderViewPager.java
deleted file mode 100644
index 2e8df42..0000000
--- a/src/com/android/messaging/ui/CustomHeaderViewPager.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.support.v4.view.PagerAdapter;
-import android.support.v4.view.ViewPager;
-import android.support.v4.view.ViewPager.OnPageChangeListener;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.widget.LinearLayout;
-
-import com.android.messaging.R;
-import com.android.messaging.util.Assert;
-
-/**
- * A view that contains both a view pager and a tab strip wrapped in a linear layout.
- */
-public class CustomHeaderViewPager extends LinearLayout {
- public final static int DEFAULT_TAB_STRIP_SIZE = -1;
- private final int mDefaultTabStripSize;
- private ViewPager mViewPager;
- private ViewPagerTabs mTabstrip;
-
- public CustomHeaderViewPager(final Context context, final AttributeSet attrs) {
- super(context, attrs);
-
- final LayoutInflater inflater = LayoutInflater.from(context);
- inflater.inflate(R.layout.custom_header_view_pager, this, true);
- setOrientation(LinearLayout.VERTICAL);
-
- mTabstrip = (ViewPagerTabs) findViewById(R.id.tab_strip);
- mViewPager = (ViewPager) findViewById(R.id.pager);
-
- TypedValue tv = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true);
- mDefaultTabStripSize = context.getResources().getDimensionPixelSize(tv.resourceId);
- }
-
- public void setCurrentItem(final int position) {
- mViewPager.setCurrentItem(position);
- }
-
- public void setViewPagerTabHeight(final int tabHeight) {
- mTabstrip.getLayoutParams().height = tabHeight == DEFAULT_TAB_STRIP_SIZE ?
- mDefaultTabStripSize : tabHeight;
- }
-
- public void setViewHolders(final CustomHeaderPagerViewHolder[] viewHolders) {
- Assert.notNull(mViewPager);
- final PagerAdapter adapter = new CustomHeaderViewPagerAdapter(viewHolders);
- mViewPager.setAdapter(adapter);
- mTabstrip.setViewPager(mViewPager);
- mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
-
- @Override
- public void onPageScrollStateChanged(int state) {
- mTabstrip.onPageScrollStateChanged(state);
- }
-
- @Override
- public void onPageScrolled(int position, float positionOffset,
- int positionOffsetPixels) {
- mTabstrip.onPageScrolled(position, positionOffset, positionOffsetPixels);
- }
-
- @Override
- public void onPageSelected(int position) {
- mTabstrip.onPageSelected(position);
- }
- });
- }
-
- public int getSelectedItemPosition() {
- return mTabstrip.getSelectedItemPosition();
- }
-}
diff --git a/src/com/android/messaging/ui/CustomHeaderViewPagerAdapter.java b/src/com/android/messaging/ui/CustomHeaderViewPagerAdapter.java
deleted file mode 100644
index 1a5cf6a..0000000
--- a/src/com/android/messaging/ui/CustomHeaderViewPagerAdapter.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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;
-
-import com.android.messaging.Factory;
-
-public class CustomHeaderViewPagerAdapter extends
- FixedViewPagerAdapter<CustomHeaderPagerViewHolder> {
-
- public CustomHeaderViewPagerAdapter(final CustomHeaderPagerViewHolder[] viewHolders) {
- super(viewHolders);
- }
-
- @Override
- public CharSequence getPageTitle(int position) {
- // The tab strip will handle RTL internally so we should use raw position.
- return getViewHolder(position, false /* rtlAware */)
- .getPageTitle(Factory.get().getApplicationContext());
- }
-}
diff --git a/src/com/android/messaging/ui/FixedViewPagerAdapter.java b/src/com/android/messaging/ui/FixedViewPagerAdapter.java
deleted file mode 100644
index bd73b71..0000000
--- a/src/com/android/messaging/ui/FixedViewPagerAdapter.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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;
-
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.support.v4.view.PagerAdapter;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.messaging.Factory;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.UiUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * A PagerAdapter that provides a fixed number of paged Views provided by a fixed set of
- * {@link PagerViewHolder}'s. This allows us to put a fixed number of Views, instead of fragments,
- * into a given ViewPager.
- */
-public class FixedViewPagerAdapter<T extends PagerViewHolder> extends PagerAdapter {
- private final T[] mViewHolders;
-
- public FixedViewPagerAdapter(final T[] viewHolders) {
- Assert.notNull(viewHolders);
- mViewHolders = viewHolders;
- }
-
- @Override
- public Object instantiateItem(final ViewGroup container, final int position) {
- final PagerViewHolder viewHolder = getViewHolder(position);
- final View view = viewHolder.getView(container);
- if (view == null) {
- return null;
- }
- view.setTag(viewHolder);
- container.addView(view);
- return viewHolder;
- }
-
- @Override
- public void destroyItem(final ViewGroup container, final int position, final Object object) {
- final PagerViewHolder viewHolder = getViewHolder(position);
- final View destroyedView = viewHolder.destroyView();
- if (destroyedView != null) {
- container.removeView(destroyedView);
- }
- }
-
- @Override
- public int getCount() {
- return mViewHolders.length;
- }
-
- @Override
- public boolean isViewFromObject(final View view, final Object object) {
- return view.getTag() == object;
- }
-
- public T getViewHolder(final int i) {
- return getViewHolder(i, true /* rtlAware */);
- }
-
- @VisibleForTesting
- public T getViewHolder(final int i, final boolean rtlAware) {
- return mViewHolders[rtlAware ? getRtlPosition(i) : i];
- }
-
- @Override
- public Parcelable saveState() {
- // The paged views in the view pager gets created and destroyed as the user scrolls through
- // them. By default, only the pages to the immediate left and right of the current visible
- // page are realized. Moreover, if the activity gets destroyed and recreated, the pages are
- // automatically destroyed. Therefore, in order to preserve transient page UI states that
- // are not persisted in the DB we'd like to store them in a Bundle when views get
- // destroyed. When the views get recreated, we rehydrate them by passing them the saved
- // data. When the activity gets destroyed, it invokes saveState() on this adapter to
- // add this saved Bundle to the overall saved instance state.
- final Bundle savedViewHolderState = new Bundle(Factory.get().getApplicationContext()
- .getClassLoader());
- for (int i = 0; i < mViewHolders.length; i++) {
- final Parcelable pageState = getViewHolder(i).saveState();
- savedViewHolderState.putParcelable(getInstanceStateKeyForPage(i), pageState);
- }
- return savedViewHolderState;
- }
-
- @Override
- public void restoreState(final Parcelable state, final ClassLoader loader) {
- if (state instanceof Bundle) {
- final Bundle restoredViewHolderState = (Bundle) state;
- ((Bundle) state).setClassLoader(Factory.get().getApplicationContext().getClassLoader());
- for (int i = 0; i < mViewHolders.length; i++) {
- final Parcelable pageState = restoredViewHolderState
- .getParcelable(getInstanceStateKeyForPage(i));
- getViewHolder(i).restoreState(pageState);
- }
- } else {
- super.restoreState(state, loader);
- }
- }
-
- public void resetState() {
- for (int i = 0; i < mViewHolders.length; i++) {
- getViewHolder(i).resetState();
- }
- }
-
- private String getInstanceStateKeyForPage(final int i) {
- return getViewHolder(i).getClass().getCanonicalName() + "_savedstate_" + i;
- }
-
- protected int getRtlPosition(final int position) {
- if (UiUtils.isRtlMode()) {
- return mViewHolders.length - 1 - position;
- }
- return position;
- }
-}
diff --git a/src/com/android/messaging/ui/ImeDetectFrameLayout.java b/src/com/android/messaging/ui/ImeDetectFrameLayout.java
deleted file mode 100644
index 32564ea..0000000
--- a/src/com/android/messaging/ui/ImeDetectFrameLayout.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-
-import com.android.messaging.util.ImeUtil;
-import com.android.messaging.util.LogUtil;
-
-public class ImeDetectFrameLayout extends FrameLayout {
- public ImeDetectFrameLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int measuredHeight = getMeasuredHeight();
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_TAG, "ImeDetectFrameLayout " +
- "measuredHeight: " + measuredHeight + " getMeasuredHeight(): " +
- getMeasuredHeight());
- }
-
- if (measuredHeight != getMeasuredHeight() && getContext() instanceof ImeUtil.ImeStateHost) {
- ((ImeUtil.ImeStateHost) getContext()).onDisplayHeightChanged(heightMeasureSpec);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/LicenseActivity.java b/src/com/android/messaging/ui/LicenseActivity.java
deleted file mode 100644
index a28da81..0000000
--- a/src/com/android/messaging/ui/LicenseActivity.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.webkit.WebView;
-
-import com.android.messaging.R;
-
-public class LicenseActivity extends Activity {
- private final String LICENSE_URL = "file:///android_asset/licenses.html";
-
- @Override
- public void onCreate(final Bundle bundle) {
- super.onCreate(bundle);
- setContentView(R.layout.license_activity);
- final WebView webView = (WebView) findViewById(R.id.content);
- webView.loadUrl(LICENSE_URL);
- }
-}
diff --git a/src/com/android/messaging/ui/LineWrapLayout.java b/src/com/android/messaging/ui/LineWrapLayout.java
deleted file mode 100644
index 5219811..0000000
--- a/src/com/android/messaging/ui/LineWrapLayout.java
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.UiUtils;
-
-import java.util.ArrayList;
-
-/**
-* A line-wrapping flow layout. Arranges children in horizontal flow, packing as many
-* child views as possible on each line. When the current line does not
-* have enough horizontal space, the layout continues on the next line.
-*/
-public class LineWrapLayout extends ViewGroup {
- public LineWrapLayout(Context context) {
- this(context, null);
- }
-
- public LineWrapLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int startPadding = UiUtils.getPaddingStart(this);
- final int endPadding = UiUtils.getPaddingEnd(this);
- final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- final int widthSize = MeasureSpec.getSize(widthMeasureSpec) - startPadding - endPadding;
- final boolean isFixedSize = (widthMode == MeasureSpec.EXACTLY);
-
- int height = 0;
-
- int childCount = getChildCount();
- int childWidthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
-
- int x = startPadding;
- int currLineWidth = 0;
- int currLineHeight = 0;
- int maxLineWidth = 0;
-
- for (int i = 0; i < childCount; i++) {
- View currChild = getChildAt(i);
- if (currChild.getVisibility() == GONE) {
- continue;
- }
- LayoutParams layoutParams = (LayoutParams) currChild.getLayoutParams();
- int startMargin = layoutParams.getStartMargin();
- int endMargin = layoutParams.getEndMargin();
- currChild.measure(childWidthSpec, MeasureSpec.UNSPECIFIED);
- int childMeasuredWidth = currChild.getMeasuredWidth() + startMargin + endMargin;
- int childMeasuredHeight = currChild.getMeasuredHeight() + layoutParams.topMargin +
- layoutParams.bottomMargin;
-
- if ((x + childMeasuredWidth) > widthSize) {
- // New line. Update the overall height and reset trackers.
- height += currLineHeight;
- currLineHeight = 0;
- x = startPadding;
- currLineWidth = 0;
- startMargin = 0;
- }
-
- x += childMeasuredWidth;
- currLineWidth += childMeasuredWidth;
- currLineHeight = Math.max(currLineHeight, childMeasuredHeight);
- maxLineWidth = Math.max(currLineWidth, maxLineWidth);
- }
- // And account for the height of the last line.
- height += currLineHeight;
-
- int width = isFixedSize ? widthSize : maxLineWidth;
- setMeasuredDimension(width + startPadding + endPadding,
- height + getPaddingTop() + getPaddingBottom());
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- final int startPadding = UiUtils.getPaddingStart(this);
- final int endPadding = UiUtils.getPaddingEnd(this);
- int width = getWidth() - startPadding - endPadding;
- int y = getPaddingTop();
- int x = startPadding;
- int childCount = getChildCount();
-
- int currLineHeight = 0;
-
- // Do a dry-run first to get the line heights.
- final ArrayList<Integer> lineHeights = new ArrayList<Integer>();
- for (int i = 0; i < childCount; i++) {
- View currChild = getChildAt(i);
- if (currChild.getVisibility() == GONE) {
- continue;
- }
- LayoutParams layoutParams = (LayoutParams) currChild.getLayoutParams();
- int childWidth = currChild.getMeasuredWidth();
- int childHeight = currChild.getMeasuredHeight();
- int startMargin = layoutParams.getStartMargin();
- int endMargin = layoutParams.getEndMargin();
-
- if ((x + childWidth + startMargin + endMargin) > width) {
- // new line
- lineHeights.add(currLineHeight);
- currLineHeight = 0;
- x = startPadding;
- startMargin = 0;
- }
- currLineHeight = Math.max(currLineHeight, childHeight + layoutParams.topMargin +
- layoutParams.bottomMargin);
- x += childWidth + startMargin + endMargin;
- }
- // Add the last line height.
- lineHeights.add(currLineHeight);
-
- // Now perform the actual layout.
- x = startPadding;
- currLineHeight = 0;
- int lineIndex = 0;
- for (int i = 0; i < childCount; i++) {
- View currChild = getChildAt(i);
- if (currChild.getVisibility() == GONE) {
- continue;
- }
- LayoutParams layoutParams = (LayoutParams) currChild.getLayoutParams();
- int childWidth = currChild.getMeasuredWidth();
- int childHeight = currChild.getMeasuredHeight();
- int startMargin = layoutParams.getStartMargin();
- int endMargin = layoutParams.getEndMargin();
-
- if ((x + childWidth + startMargin + endMargin) > width) {
- // new line
- y += currLineHeight;
- currLineHeight = 0;
- x = startPadding;
- startMargin = 0;
- lineIndex++;
- }
- final int startPositionX = x + startMargin;
- int startPositionY = y + layoutParams.topMargin; // default to top gravity
- final int majorGravity = layoutParams.gravity & Gravity.VERTICAL_GRAVITY_MASK;
- if (majorGravity != Gravity.TOP && lineHeights.size() > lineIndex) {
- final int lineHeight = lineHeights.get(lineIndex);
- switch (majorGravity) {
- case Gravity.BOTTOM:
- startPositionY = y + lineHeight - childHeight - layoutParams.bottomMargin;
- break;
-
- case Gravity.CENTER_VERTICAL:
- startPositionY = y + (lineHeight - childHeight) / 2;
- break;
- }
- }
-
- if (OsUtil.isAtLeastJB_MR2() && getResources().getConfiguration()
- .getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
- currChild.layout(width - startPositionX - childWidth, startPositionY,
- width - startPositionX, startPositionY + childHeight);
- } else {
- currChild.layout(startPositionX, startPositionY, startPositionX + childWidth,
- startPositionY + childHeight);
- }
- currLineHeight = Math.max(currLineHeight, childHeight + layoutParams.topMargin +
- layoutParams.bottomMargin);
- x += childWidth + startMargin + endMargin;
- }
- }
-
- @Override
- protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
- return new LayoutParams(p);
- }
-
- @Override
- public LayoutParams generateLayoutParams(AttributeSet attrs) {
- return new LayoutParams(getContext(), attrs);
- }
-
- @Override
- protected LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
- }
-
- public static final class LayoutParams extends FrameLayout.LayoutParams {
- public LayoutParams(Context c, AttributeSet attrs) {
- super(c, attrs);
- }
-
- public LayoutParams(int width, int height) {
- super(width, height);
- }
-
- public LayoutParams(ViewGroup.LayoutParams source) {
- super(source);
- }
-
- public int getStartMargin() {
- if (OsUtil.isAtLeastJB_MR2()) {
- return getMarginStart();
- } else {
- return leftMargin;
- }
- }
-
- public int getEndMargin() {
- if (OsUtil.isAtLeastJB_MR2()) {
- return getMarginEnd();
- } else {
- return rightMargin;
- }
- }
- }
-}
diff --git a/src/com/android/messaging/ui/ListEmptyView.java b/src/com/android/messaging/ui/ListEmptyView.java
deleted file mode 100644
index 8cf3049..0000000
--- a/src/com/android/messaging/ui/ListEmptyView.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.LinearLayout.LayoutParams;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-
-/**
- * A common reusable view that shows a hint image and text for an empty list view.
- */
-public class ListEmptyView extends LinearLayout {
- private ImageView mEmptyImageHint;
- private TextView mEmptyTextHint;
-
- public ListEmptyView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- mEmptyImageHint = (ImageView) findViewById(R.id.empty_image_hint);
- mEmptyTextHint = (TextView) findViewById(R.id.empty_text_hint);
- }
-
- public void setImageHint(final int resId) {
- mEmptyImageHint.setImageResource(resId);
- }
-
- public void setTextHint(final int resId) {
- mEmptyTextHint.setText(getResources().getText(resId));
- }
-
- public void setTextHint(final CharSequence hintText) {
- mEmptyTextHint.setText(hintText);
- }
-
- public void setIsImageVisible(final boolean isImageVisible) {
- mEmptyImageHint.setVisibility(isImageVisible ? VISIBLE : GONE);
- }
-
- public void setIsVerticallyCentered(final boolean isVerticallyCentered) {
- int gravity =
- isVerticallyCentered ? Gravity.CENTER : Gravity.TOP | Gravity.CENTER_HORIZONTAL;
- ((LinearLayout.LayoutParams) mEmptyImageHint.getLayoutParams()).gravity = gravity;
- ((LinearLayout.LayoutParams) mEmptyTextHint.getLayoutParams()).gravity = gravity;
- getLayoutParams().height =
- isVerticallyCentered ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT;
- requestLayout();
- }
-}
diff --git a/src/com/android/messaging/ui/MaxHeightScrollView.java b/src/com/android/messaging/ui/MaxHeightScrollView.java
deleted file mode 100644
index a000cd1..0000000
--- a/src/com/android/messaging/ui/MaxHeightScrollView.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.util.AttributeSet;
-import android.widget.ScrollView;
-
-import com.android.messaging.R;
-
-/**
- * A ScrollView that limits the maximum height that it can take. This is to work around android
- * layout's limitation of not having android:maxHeight.
- */
-public class MaxHeightScrollView extends ScrollView {
- private static final int NO_MAX_HEIGHT = -1;
-
- private final int mMaxHeight;
-
- public MaxHeightScrollView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- final TypedArray attr = context.obtainStyledAttributes(attrs,
- R.styleable.MaxHeightScrollView, 0, 0);
- mMaxHeight = attr.getDimensionPixelSize(R.styleable.MaxHeightScrollView_android_maxHeight,
- NO_MAX_HEIGHT);
- attr.recycle();
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mMaxHeight != NO_MAX_HEIGHT) {
- setMeasuredDimension(getMeasuredWidth(), Math.min(getMeasuredHeight(), mMaxHeight));
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/MultiAttachmentLayout.java b/src/com/android/messaging/ui/MultiAttachmentLayout.java
deleted file mode 100644
index f620245..0000000
--- a/src/com/android/messaging/ui/MultiAttachmentLayout.java
+++ /dev/null
@@ -1,424 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.animation.AnimationSet;
-import android.view.animation.ScaleAnimation;
-import android.view.animation.TranslateAnimation;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.MediaPickerMessagePartData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.PendingAttachmentData;
-import com.android.messaging.datamodel.media.ImageRequestDescriptor;
-import com.android.messaging.ui.AsyncImageView.AsyncImageViewDelayLoader;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.UiUtils;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Holds and displays multiple attachments in a 4x2 grid. Each preview image "tile" can take
- * one of three sizes - small (1x1), wide (2x1) and large (2x2). We have a number of predefined
- * layout settings designed for holding 2, 3, 4+ attachments (these layout settings are
- * tweakable by design request to allow for max flexibility). For a visual example, consider the
- * following attachment layout:
- *
- * +---------------+----------------+
- * | | |
- * | | B |
- * | | |
- * | A |-------+--------|
- * | | | |
- * | | C | D |
- * | | | |
- * +---------------+-------+--------+
- *
- * In the above example, the layout consists of four tiles, A-D. A is a large tile, B is a
- * wide tile and C & D are both small tiles. A starts at (0,0) and ends at (1,1), B starts at
- * (2,0) and ends at (3,0), and so on. In our layout class we'd have these tiles in the order
- * of A-D, so that we make sure the last tile is always the one where we can put the overflow
- * indicator (e.g. "+2").
- */
-public class MultiAttachmentLayout extends FrameLayout {
-
- public interface OnAttachmentClickListener {
- boolean onAttachmentClick(MessagePartData attachment, Rect viewBoundsOnScreen,
- boolean longPress);
- }
-
- private static final int GRID_WIDTH = 4; // in # of cells
- private static final int GRID_HEIGHT = 2; // in # of cells
-
- /**
- * Represents a preview image tile in the layout
- */
- private static class Tile {
- public final int startX;
- public final int startY;
- public final int endX;
- public final int endY;
-
- private Tile(final int startX, final int startY, final int endX, final int endY) {
- this.startX = startX;
- this.startY = startY;
- this.endX = endX;
- this.endY = endY;
- }
-
- public int getWidthMeasureSpec(final int cellWidth, final int padding) {
- return MeasureSpec.makeMeasureSpec((endX - startX + 1) * cellWidth - padding * 2,
- MeasureSpec.EXACTLY);
- }
-
- public int getHeightMeasureSpec(final int cellHeight, final int padding) {
- return MeasureSpec.makeMeasureSpec((endY - startY + 1) * cellHeight - padding * 2,
- MeasureSpec.EXACTLY);
- }
-
- public static Tile large(final int startX, final int startY) {
- return new Tile(startX, startY, startX + 1, startY + 1);
- }
-
- public static Tile wide(final int startX, final int startY) {
- return new Tile(startX, startY, startX + 1, startY);
- }
-
- public static Tile small(final int startX, final int startY) {
- return new Tile(startX, startY, startX, startY);
- }
- }
-
- /**
- * A layout simply contains a list of tiles, in the order of top-left -> bottom-right.
- */
- private static class Layout {
- public final List<Tile> tiles;
- public Layout(final Tile[] tilesArray) {
- tiles = Arrays.asList(tilesArray);
- }
- }
-
- /**
- * List of predefined layout configurations w.r.t no. of attachments.
- */
- private static final Layout[] ATTACHMENT_LAYOUTS_BY_COUNT = {
- null, // Doesn't support zero attachments.
- null, // Doesn't support one attachment. Single attachment preview is used instead.
- new Layout(new Tile[] { Tile.large(0, 0), Tile.large(2, 0) }), // 2 items
- new Layout(new Tile[] { Tile.large(0, 0), Tile.wide(2, 0), Tile.wide(2, 1) }), // 3 items
- new Layout(new Tile[] { Tile.large(0, 0), Tile.wide(2, 0), Tile.small(2, 1), // 4+ items
- Tile.small(3, 1) }),
- };
-
- /**
- * List of predefined RTL layout configurations w.r.t no. of attachments.
- */
- private static final Layout[] ATTACHMENT_RTL_LAYOUTS_BY_COUNT = {
- null, // Doesn't support zero attachments.
- null, // Doesn't support one attachment. Single attachment preview is used instead.
- new Layout(new Tile[] { Tile.large(2, 0), Tile.large(0, 0)}), // 2 items
- new Layout(new Tile[] { Tile.large(2, 0), Tile.wide(0, 0), Tile.wide(0, 1) }), // 3 items
- new Layout(new Tile[] { Tile.large(2, 0), Tile.wide(0, 0), Tile.small(1, 1), // 4+ items
- Tile.small(0, 1) }),
- };
-
- private Layout mCurrentLayout;
- private ArrayList<ViewWrapper> mPreviewViews;
- private int mPlusNumber;
- private TextView mPlusTextView;
- private OnAttachmentClickListener mAttachmentClickListener;
- private AsyncImageViewDelayLoader mImageViewDelayLoader;
-
- public MultiAttachmentLayout(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mPreviewViews = new ArrayList<ViewWrapper>();
- }
-
- public void bindAttachments(final Iterable<MessagePartData> attachments,
- final Rect transitionRect, final int count) {
- final ArrayList<ViewWrapper> previousViews = mPreviewViews;
- mPreviewViews = new ArrayList<ViewWrapper>();
- removeView(mPlusTextView);
- mPlusTextView = null;
-
- determineLayout(attachments, count);
- buildViews(attachments, previousViews, transitionRect);
-
- // Remove all previous views that couldn't be recycled.
- for (final ViewWrapper viewWrapper : previousViews) {
- removeView(viewWrapper.view);
- }
- requestLayout();
- }
-
- public OnAttachmentClickListener getOnAttachmentClickListener() {
- return mAttachmentClickListener;
- }
-
- public void setOnAttachmentClickListener(final OnAttachmentClickListener listener) {
- mAttachmentClickListener = listener;
- }
-
- public void setImageViewDelayLoader(final AsyncImageViewDelayLoader delayLoader) {
- mImageViewDelayLoader = delayLoader;
- }
-
- public void setColorFilter(int color) {
- for (ViewWrapper viewWrapper : mPreviewViews) {
- if (viewWrapper.view instanceof AsyncImageView) {
- ((AsyncImageView) viewWrapper.view).setColorFilter(color);
- }
- }
- }
-
- public void clearColorFilter() {
- for (ViewWrapper viewWrapper : mPreviewViews) {
- if (viewWrapper.view instanceof AsyncImageView) {
- ((AsyncImageView) viewWrapper.view).clearColorFilter();
- }
- }
- }
-
- private void determineLayout(final Iterable<MessagePartData> attachments, final int count) {
- Assert.isTrue(attachments != null);
- final boolean isRtl = AccessibilityUtil.isLayoutRtl(getRootView());
- if (isRtl) {
- mCurrentLayout = ATTACHMENT_RTL_LAYOUTS_BY_COUNT[Math.min(count,
- ATTACHMENT_RTL_LAYOUTS_BY_COUNT.length - 1)];
- } else {
- mCurrentLayout = ATTACHMENT_LAYOUTS_BY_COUNT[Math.min(count,
- ATTACHMENT_LAYOUTS_BY_COUNT.length - 1)];
- }
-
- // We must have a valid layout for the current configuration.
- Assert.notNull(mCurrentLayout);
-
- mPlusNumber = count - mCurrentLayout.tiles.size();
- Assert.isTrue(mPlusNumber >= 0);
- }
-
- private void buildViews(final Iterable<MessagePartData> attachments,
- final ArrayList<ViewWrapper> previousViews, final Rect transitionRect) {
- final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
- final int count = mCurrentLayout.tiles.size();
- int i = 0;
- final Iterator<MessagePartData> iterator = attachments.iterator();
- while (iterator.hasNext() && i < count) {
- final MessagePartData attachment = iterator.next();
- ViewWrapper attachmentWrapper = null;
- // Try to recycle a previous view first
- for (int j = 0; j < previousViews.size(); j++) {
- final ViewWrapper previousView = previousViews.get(j);
- if (previousView.attachment.equals(attachment) &&
- !(previousView.attachment instanceof PendingAttachmentData)) {
- attachmentWrapper = previousView;
- previousViews.remove(j);
- break;
- }
- }
-
- if (attachmentWrapper == null) {
- final View view = AttachmentPreviewFactory.createAttachmentPreview(layoutInflater,
- attachment, this, AttachmentPreviewFactory.TYPE_MULTIPLE,
- false /* startImageRequest */, mAttachmentClickListener);
-
- if (view == null) {
- // createAttachmentPreview can return null if something goes wrong (e.g.
- // attachment has unsupported contentType)
- continue;
- }
- if (view instanceof AsyncImageView && mImageViewDelayLoader != null) {
- AsyncImageView asyncImageView = (AsyncImageView) view;
- asyncImageView.setDelayLoader(mImageViewDelayLoader);
- }
- addView(view);
- attachmentWrapper = new ViewWrapper(view, attachment);
- // Help animate from single to multi by copying over the prev location
- if (count == 2 && i == 1 && transitionRect != null) {
- attachmentWrapper.prevLeft = transitionRect.left;
- attachmentWrapper.prevTop = transitionRect.top;
- attachmentWrapper.prevWidth = transitionRect.width();
- attachmentWrapper.prevHeight = transitionRect.height();
- }
- }
- i++;
- Assert.notNull(attachmentWrapper);
- mPreviewViews.add(attachmentWrapper);
-
- // The first view will animate in using PopupTransitionAnimation, but the remaining
- // views will slide from their previous position to their new position within the
- // layout
- if (i == 0) {
- AttachmentPreview.tryAnimateViewIn(attachment, attachmentWrapper.view);
- }
- attachmentWrapper.needsSlideAnimation = i > 0;
- }
-
- // Build the plus text view (e.g. "+2") for when there are more attachments than what
- // this layout can display.
- if (mPlusNumber > 0) {
- mPlusTextView = (TextView) layoutInflater.inflate(R.layout.attachment_more_text_view,
- null /* parent */);
- mPlusTextView.setText(getResources().getString(R.string.attachment_more_items,
- mPlusNumber));
- addView(mPlusTextView);
- }
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- final int maxWidth = getResources().getDimensionPixelSize(
- R.dimen.multiple_attachment_preview_width);
- final int maxHeight = getResources().getDimensionPixelSize(
- R.dimen.multiple_attachment_preview_height);
- final int width = Math.min(MeasureSpec.getSize(widthMeasureSpec), maxWidth);
- final int height = maxHeight;
- final int cellWidth = width / GRID_WIDTH;
- final int cellHeight = height / GRID_HEIGHT;
- final int count = mPreviewViews.size();
- final int padding = getResources().getDimensionPixelOffset(
- R.dimen.multiple_attachment_preview_padding);
- for (int i = 0; i < count; i++) {
- final View view = mPreviewViews.get(i).view;
- final Tile imageTile = mCurrentLayout.tiles.get(i);
- view.measure(imageTile.getWidthMeasureSpec(cellWidth, padding),
- imageTile.getHeightMeasureSpec(cellHeight, padding));
-
- // Now that we know the size, we can request an appropriately-sized image.
- if (view instanceof AsyncImageView) {
- final ImageRequestDescriptor imageRequest =
- AttachmentPreviewFactory.getImageRequestDescriptorForAttachment(
- mPreviewViews.get(i).attachment,
- view.getMeasuredWidth(),
- view.getMeasuredHeight());
- ((AsyncImageView) view).setImageResourceId(imageRequest);
- }
-
- if (i == count - 1 && mPlusTextView != null) {
- // The plus text view always covers the last attachment.
- mPlusTextView.measure(imageTile.getWidthMeasureSpec(cellWidth, padding),
- imageTile.getHeightMeasureSpec(cellHeight, padding));
- }
- }
- setMeasuredDimension(width, height);
- }
-
- @Override
- protected void onLayout(final boolean changed, final int left, final int top, final int right,
- final int bottom) {
- final int cellWidth = getMeasuredWidth() / GRID_WIDTH;
- final int cellHeight = getMeasuredHeight() / GRID_HEIGHT;
- final int padding = getResources().getDimensionPixelOffset(
- R.dimen.multiple_attachment_preview_padding);
- final int count = mPreviewViews.size();
- for (int i = 0; i < count; i++) {
- final ViewWrapper viewWrapper = mPreviewViews.get(i);
- final View view = viewWrapper.view;
- final Tile imageTile = mCurrentLayout.tiles.get(i);
- final int tileLeft = imageTile.startX * cellWidth;
- final int tileTop = imageTile.startY * cellHeight;
- view.layout(tileLeft + padding, tileTop + padding,
- tileLeft + view.getMeasuredWidth(),
- tileTop + view.getMeasuredHeight());
- if (viewWrapper.needsSlideAnimation) {
- trySlideAttachmentView(viewWrapper);
- viewWrapper.needsSlideAnimation = false;
- } else {
- viewWrapper.prevLeft = view.getLeft();
- viewWrapper.prevTop = view.getTop();
- viewWrapper.prevWidth = view.getWidth();
- viewWrapper.prevHeight = view.getHeight();
- }
-
- if (i == count - 1 && mPlusTextView != null) {
- // The plus text view always covers the last attachment.
- mPlusTextView.layout(tileLeft + padding, tileTop + padding,
- tileLeft + mPlusTextView.getMeasuredWidth(),
- tileTop + mPlusTextView.getMeasuredHeight());
- }
- }
- }
-
- private void trySlideAttachmentView(final ViewWrapper viewWrapper) {
- if (!(viewWrapper.attachment instanceof MediaPickerMessagePartData)) {
- return;
- }
- final View view = viewWrapper.view;
-
-
- final int xOffset = viewWrapper.prevLeft - view.getLeft();
- final int yOffset = viewWrapper.prevTop - view.getTop();
- final float scaleX = viewWrapper.prevWidth / (float) view.getWidth();
- final float scaleY = viewWrapper.prevHeight / (float) view.getHeight();
-
- if (xOffset == 0 && yOffset == 0 && scaleX == 1 && scaleY == 1) {
- // Layout hasn't changed
- return;
- }
-
- final AnimationSet animationSet = new AnimationSet(
- true /* shareInterpolator */);
- animationSet.addAnimation(new TranslateAnimation(xOffset, 0, yOffset, 0));
- animationSet.addAnimation(new ScaleAnimation(scaleX, 1, scaleY, 1));
- animationSet.setDuration(
- UiUtils.MEDIAPICKER_TRANSITION_DURATION);
- animationSet.setInterpolator(UiUtils.DEFAULT_INTERPOLATOR);
- view.startAnimation(animationSet);
- view.invalidate();
- viewWrapper.prevLeft = view.getLeft();
- viewWrapper.prevTop = view.getTop();
- viewWrapper.prevWidth = view.getWidth();
- viewWrapper.prevHeight = view.getHeight();
- }
-
- public View findViewForAttachment(final MessagePartData attachment) {
- for (ViewWrapper wrapper : mPreviewViews) {
- if (wrapper.attachment.equals(attachment) &&
- !(wrapper.attachment instanceof PendingAttachmentData)) {
- return wrapper.view;
- }
- }
- return null;
- }
-
- private static class ViewWrapper {
- final View view;
- final MessagePartData attachment;
- boolean needsSlideAnimation;
- int prevLeft;
- int prevTop;
- int prevWidth;
- int prevHeight;
-
- ViewWrapper(final View view, final MessagePartData attachment) {
- this.view = view;
- this.attachment = attachment;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/OrientedBitmapDrawable.java b/src/com/android/messaging/ui/OrientedBitmapDrawable.java
deleted file mode 100644
index 9242668..0000000
--- a/src/com/android/messaging/ui/OrientedBitmapDrawable.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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;
-
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.BitmapDrawable;
-import android.view.Gravity;
-
-import com.android.messaging.util.exif.ExifInterface;
-
-/**
- * A drawable that draws a bitmap in a flipped or rotated orientation without having to adjust the
- * bitmap
- */
-public class OrientedBitmapDrawable extends BitmapDrawable {
- private final ExifInterface.OrientationParams mOrientationParams;
- private final Rect mDstRect;
- private int mCenterX;
- private int mCenterY;
- private boolean mApplyGravity;
-
- public static BitmapDrawable create(final int orientation, Resources res, Bitmap bitmap) {
- if (orientation <= ExifInterface.Orientation.TOP_LEFT) {
- // No need to adjust the bitmap, so just use a regular BitmapDrawable
- return new BitmapDrawable(res, bitmap);
- } else {
- // Create an oriented bitmap drawable
- return new OrientedBitmapDrawable(orientation, res, bitmap);
- }
- }
-
- private OrientedBitmapDrawable(final int orientation, Resources res, Bitmap bitmap) {
- super(res, bitmap);
- mOrientationParams = ExifInterface.getOrientationParams(orientation);
- mApplyGravity = true;
- mDstRect = new Rect();
- }
-
- @Override
- public int getIntrinsicWidth() {
- if (mOrientationParams.invertDimensions) {
- return super.getIntrinsicHeight();
- }
- return super.getIntrinsicWidth();
- }
-
- @Override
- public int getIntrinsicHeight() {
- if (mOrientationParams.invertDimensions) {
- return super.getIntrinsicWidth();
- }
- return super.getIntrinsicHeight();
- }
-
- @Override
- protected void onBoundsChange(Rect bounds) {
- super.onBoundsChange(bounds);
- mApplyGravity = true;
- }
-
- @Override
- public void draw(Canvas canvas) {
- if (mApplyGravity) {
- Gravity.apply(getGravity(), getIntrinsicWidth(), getIntrinsicHeight(), getBounds(),
- mDstRect);
- mCenterX = mDstRect.centerX();
- mCenterY = mDstRect.centerY();
- if (mOrientationParams.invertDimensions) {
- final Matrix matrix = new Matrix();
- matrix.setRotate(mOrientationParams.rotation, mCenterX, mCenterY);
- final RectF rotatedRect = new RectF(mDstRect);
- matrix.mapRect(rotatedRect);
- mDstRect.set((int) rotatedRect.left, (int) rotatedRect.top, (int) rotatedRect.right,
- (int) rotatedRect.bottom);
- }
-
- mApplyGravity = false;
- }
- canvas.save();
- canvas.scale(mOrientationParams.scaleX, mOrientationParams.scaleY, mCenterX, mCenterY);
- canvas.rotate(mOrientationParams.rotation, mCenterX, mCenterY);
- canvas.drawBitmap(getBitmap(), (Rect) null, mDstRect, getPaint());
- canvas.restore();
- }
-}
diff --git a/src/com/android/messaging/ui/PagerViewHolder.java b/src/com/android/messaging/ui/PagerViewHolder.java
deleted file mode 100644
index 2f33a0f..0000000
--- a/src/com/android/messaging/ui/PagerViewHolder.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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;
-
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * Holds reusable View(s) for a {@link FixedViewPagerAdapter} to display a page. By using
- * reusable Views inside ViewPagers this allows us to get rid of nested fragments and the messy
- * activity lifecycle problems they entail.
- */
-public interface PagerViewHolder extends PersistentInstanceState {
- /** Instructs the pager to clean up any view related resources
- * @return the destroyed View so that the adapter may remove it from the container, or
- * null if no View has been created. */
- View destroyView();
-
- /** @return The view that presents the page view to the user */
- View getView(ViewGroup container);
-}
diff --git a/src/com/android/messaging/ui/PagingAwareViewPager.java b/src/com/android/messaging/ui/PagingAwareViewPager.java
deleted file mode 100644
index cc7b2cd..0000000
--- a/src/com/android/messaging/ui/PagingAwareViewPager.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.support.v4.view.PagerAdapter;
-import android.support.v4.view.ViewPager;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-
-import com.android.messaging.util.UiUtils;
-
-/**
- * A simple extension on the standard ViewPager which lets you turn paging on/off.
- */
-public class PagingAwareViewPager extends ViewPager {
- private boolean mPagingEnabled = true;
-
- public PagingAwareViewPager(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- public void setCurrentItem(int item, boolean smoothScroll) {
- super.setCurrentItem(getRtlPosition(item), smoothScroll);
- }
-
- @Override
- public void setCurrentItem(int item) {
- super.setCurrentItem(getRtlPosition(item));
- }
-
- @Override
- public int getCurrentItem() {
- int position = super.getCurrentItem();
- return getRtlPosition(position);
- }
-
- /**
- * Switches position in pager to be adjusted for if we are in RtL mode
- *
- * @param position
- * @return position adjusted if in rtl mode
- */
- protected int getRtlPosition(final int position) {
- final PagerAdapter adapter = getAdapter();
- if (adapter != null && UiUtils.isRtlMode()) {
- return adapter.getCount() - 1 - position;
- }
- return position;
- }
-
- @Override
- public boolean onTouchEvent(final MotionEvent event) {
- if (!mPagingEnabled) {
- return false;
- }
- return super.onTouchEvent(event);
- }
-
- @Override
- public boolean onInterceptTouchEvent(final MotionEvent event) {
- if (!mPagingEnabled) {
- return false;
- }
- return super.onInterceptTouchEvent(event);
- }
-
- public void setPagingEnabled(final boolean enabled) {
- this.mPagingEnabled = enabled;
- }
-
- /** This prevents touch-less scrolling eg. while doing accessibility navigation. */
- @Override
- public boolean canScrollHorizontally(int direction) {
- if (mPagingEnabled) {
- return super.canScrollHorizontally(direction);
- } else {
- return false;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/PermissionCheckActivity.java b/src/com/android/messaging/ui/PermissionCheckActivity.java
deleted file mode 100644
index e992a10..0000000
--- a/src/com/android/messaging/ui/PermissionCheckActivity.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * 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;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.TextView;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.UiUtils;
-
-/**
- * Activity to check if the user has required permissions. If not, it will try to prompt the user
- * to grant permissions. However, the OS may not actually prompt the user if the user had
- * previously checked the "Never ask again" checkbox while denying the required permissions.
- */
-public class PermissionCheckActivity extends Activity {
- private static final int REQUIRED_PERMISSIONS_REQUEST_CODE = 1;
- private static final long AUTOMATED_RESULT_THRESHOLD_MILLLIS = 250;
- private static final String PACKAGE_URI_PREFIX = "package:";
- private long mRequestTimeMillis;
- private TextView mNextView;
- private TextView mSettingsView;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (redirectIfNeeded()) {
- return;
- }
-
- setContentView(R.layout.permission_check_activity);
- UiUtils.setStatusBarColor(this, getColor(R.color.permission_check_activity_background));
-
- findViewById(R.id.exit).setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View view) {
- finish();
- }
- });
-
- mNextView = (TextView) findViewById(R.id.next);
- mNextView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View view) {
- tryRequestPermission();
- }
- });
-
- mSettingsView = (TextView) findViewById(R.id.settings);
- mSettingsView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View view) {
- final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
- Uri.parse(PACKAGE_URI_PREFIX + getPackageName()));
- startActivity(intent);
- }
- });
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- if (redirectIfNeeded()) {
- return;
- }
- }
-
- private void tryRequestPermission() {
- final String[] missingPermissions = OsUtil.getMissingRequiredPermissions();
- if (missingPermissions.length == 0) {
- redirect();
- return;
- }
-
- mRequestTimeMillis = SystemClock.elapsedRealtime();
- requestPermissions(missingPermissions, REQUIRED_PERMISSIONS_REQUEST_CODE);
- }
-
- @Override
- public void onRequestPermissionsResult(
- final int requestCode, final String permissions[], final int[] grantResults) {
- if (requestCode == REQUIRED_PERMISSIONS_REQUEST_CODE) {
- // We do not use grantResults as some of the granted permissions might have been
- // revoked while the permissions dialog box was being shown for the missing permissions.
- if (OsUtil.hasRequiredPermissions()) {
- Factory.get().onRequiredPermissionsAcquired();
- redirect();
- } else {
- final long currentTimeMillis = SystemClock.elapsedRealtime();
- // If the permission request completes very quickly, it must be because the system
- // automatically denied. This can happen if the user had previously denied it
- // and checked the "Never ask again" check box.
- if ((currentTimeMillis - mRequestTimeMillis) < AUTOMATED_RESULT_THRESHOLD_MILLLIS) {
- mNextView.setVisibility(View.GONE);
-
- mSettingsView.setVisibility(View.VISIBLE);
- findViewById(R.id.enable_permission_procedure).setVisibility(View.VISIBLE);
- }
- }
- }
- }
-
- /** Returns true if the redirecting was performed */
- private boolean redirectIfNeeded() {
- if (!OsUtil.hasRequiredPermissions()) {
- return false;
- }
-
- redirect();
- return true;
- }
-
- private void redirect() {
- UIIntents.get().launchConversationListActivity(this);
- finish();
- }
-}
diff --git a/src/com/android/messaging/ui/PersistentInstanceState.java b/src/com/android/messaging/ui/PersistentInstanceState.java
deleted file mode 100644
index 3cc1856..0000000
--- a/src/com/android/messaging/ui/PersistentInstanceState.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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;
-
-import android.os.Parcelable;
-
-/**
- * Wraps around functionality to save, restore and reset a particular UI component's state.
- */
-public interface PersistentInstanceState {
- /**
- * Saves necessary information about the current state of the instance to later restore
- * the instance to its original state.
- */
- Parcelable saveState();
-
- /**
- * Given a previously saved instance state, attempt to restore to its original view state.
- */
- void restoreState(Parcelable restoredState);
-
- /**
- * Called when any current/preserved state should be reset to zero state.
- */
- void resetState();
-}
diff --git a/src/com/android/messaging/ui/PersonItemView.java b/src/com/android/messaging/ui/PersonItemView.java
deleted file mode 100644
index afd1a99..0000000
--- a/src/com/android/messaging/ui/PersonItemView.java
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.content.Intent;
-import android.support.v4.text.BidiFormatter;
-import android.support.v4.text.TextDirectionHeuristicsCompat;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnLayoutChangeListener;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.binding.DetachableBinding;
-import com.android.messaging.datamodel.data.PersonItemData;
-import com.android.messaging.datamodel.data.PersonItemData.PersonItemDataListener;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.UiUtils;
-
-/**
- * Shows a view for a "person" - could be a contact or a participant. This always shows a
- * contact icon on the left, and the person's display name on the right.
- *
- * This view is always bound to an abstract PersonItemData class, so to use it for a specific
- * scenario, all you need to do is to create a concrete PersonItemData subclass that bridges
- * between the underlying data (e.g. ParticipantData) and what the UI wants (e.g. display name).
- */
-public class PersonItemView extends LinearLayout implements PersonItemDataListener,
- OnLayoutChangeListener {
- public interface PersonItemViewListener {
- void onPersonClicked(PersonItemData data);
- boolean onPersonLongClicked(PersonItemData data);
- }
-
- protected final DetachableBinding<PersonItemData> mBinding;
- private TextView mNameTextView;
- private TextView mDetailsTextView;
- private ContactIconView mContactIconView;
- private View mDetailsContainer;
- private PersonItemViewListener mListener;
- private boolean mAvatarOnly;
-
- public PersonItemView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mBinding = BindingBase.createDetachableBinding(this);
- LayoutInflater.from(getContext()).inflate(R.layout.person_item_view, this, true);
- }
-
- @Override
- protected void onFinishInflate() {
- mNameTextView = (TextView) findViewById(R.id.name);
- mDetailsTextView = (TextView) findViewById(R.id.details);
- mContactIconView = (ContactIconView) findViewById(R.id.contact_icon);
- mDetailsContainer = findViewById(R.id.details_container);
- mNameTextView.addOnLayoutChangeListener(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- if (mBinding.isBound()) {
- mBinding.detach();
- }
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mBinding.reAttachIfPossible();
- }
-
- /**
- * Binds to a person item data which will provide us info to be displayed.
- * @param personData the PersonItemData to be bound to.
- */
- public void bind(final PersonItemData personData) {
- if (mBinding.isBound()) {
- if (mBinding.getData().equals(personData)) {
- // Don't rebind if we are requesting the same data.
- return;
- }
- mBinding.unbind();
- }
-
- if (personData != null) {
- mBinding.bind(personData);
- mBinding.getData().setListener(this);
-
- // Accessibility reason : in case phone numbers are mixed in the display name,
- // we need to vocalize it for talkback.
- final String vocalizedDisplayName = AccessibilityUtil.getVocalizedPhoneNumber(
- getResources(), getDisplayName());
- mNameTextView.setContentDescription(vocalizedDisplayName);
- }
- updateViewAppearance();
- }
-
- /**
- * @return Display name, possibly comma-ellipsized.
- */
- private String getDisplayName() {
- final int width = mNameTextView.getMeasuredWidth();
- final String displayName = mBinding.getData().getDisplayName();
- if (width == 0 || TextUtils.isEmpty(displayName) || !displayName.contains(",")) {
- return displayName;
- }
- final String plusOneString = getContext().getString(R.string.plus_one);
- final String plusNString = getContext().getString(R.string.plus_n);
- return BidiFormatter.getInstance().unicodeWrap(
- UiUtils.commaEllipsize(
- displayName,
- mNameTextView.getPaint(),
- width,
- plusOneString,
- plusNString).toString(),
- TextDirectionHeuristicsCompat.LTR);
- }
-
- @Override
- public void onLayoutChange(final View v, final int left, final int top, final int right,
- final int bottom, final int oldLeft, final int oldTop, final int oldRight,
- final int oldBottom) {
- if (mBinding.isBound() && v == mNameTextView) {
- setNameTextView();
- }
- }
-
- /**
- * When set to true, we display only the avatar of the person and hide everything else.
- */
- public void setAvatarOnly(final boolean avatarOnly) {
- mAvatarOnly = avatarOnly;
- mDetailsContainer.setVisibility(avatarOnly ? GONE : VISIBLE);
- }
-
- public boolean isAvatarOnly() {
- return mAvatarOnly;
- }
-
- public void setNameTextColor(final int color) {
- mNameTextView.setTextColor(color);
- }
-
- public void setDetailsTextColor(final int color) {
- mDetailsTextView.setTextColor(color);
- }
-
- public void setListener(final PersonItemViewListener listener) {
- mListener = listener;
- if (mListener == null) {
- return;
- }
- setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View v) {
- if (mListener != null && mBinding.isBound()) {
- mListener.onPersonClicked(mBinding.getData());
- }
- }
- });
- final OnLongClickListener onLongClickListener = new OnLongClickListener() {
- @Override
- public boolean onLongClick(View v) {
- if (mListener != null && mBinding.isBound()) {
- return mListener.onPersonLongClicked(mBinding.getData());
- }
- return false;
- }
- };
- setOnLongClickListener(onLongClickListener);
- mContactIconView.setOnLongClickListener(onLongClickListener);
- }
-
- public void performClickOnAvatar() {
- mContactIconView.performClick();
- }
-
- protected void updateViewAppearance() {
- if (mBinding.isBound()) {
- setNameTextView();
-
- final String details = mBinding.getData().getDetails();
- if (TextUtils.isEmpty(details)) {
- mDetailsTextView.setVisibility(GONE);
- } else {
- mDetailsTextView.setVisibility(VISIBLE);
- mDetailsTextView.setText(details);
- }
-
- mContactIconView.setImageResourceUri(mBinding.getData().getAvatarUri(),
- mBinding.getData().getContactId(), mBinding.getData().getLookupKey(),
- mBinding.getData().getNormalizedDestination());
- } else {
- mNameTextView.setText("");
- mContactIconView.setImageResourceUri(null);
- }
- }
-
- private void setNameTextView() {
- final String displayName = getDisplayName();
- if (TextUtils.isEmpty(displayName)) {
- mNameTextView.setVisibility(GONE);
- } else {
- mNameTextView.setVisibility(VISIBLE);
- mNameTextView.setText(displayName);
- }
- }
-
- @Override
- public void onPersonDataUpdated(final PersonItemData data) {
- mBinding.ensureBound(data);
- updateViewAppearance();
- }
-
- @Override
- public void onPersonDataFailed(final PersonItemData data, final Exception exception) {
- mBinding.ensureBound(data);
- updateViewAppearance();
- }
-
- public Intent getClickIntent() {
- return mBinding.getData().getClickIntent();
- }
-}
diff --git a/src/com/android/messaging/ui/PlaceholderInsetDrawable.java b/src/com/android/messaging/ui/PlaceholderInsetDrawable.java
deleted file mode 100644
index dda2e7b..0000000
--- a/src/com/android/messaging/ui/PlaceholderInsetDrawable.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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;
-
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.InsetDrawable;
-
-/**
- * A "placeholder" drawable that has the same sizing properties as the real UI element it
- * replaces.
- *
- * This is an InsetDrawable that takes a placeholder drawable (an animation list, or simply
- * a color drawable) and place it in the center of the inset drawable that's sized to the
- * requested source width and height of the image that it replaces. Unlike the base
- * InsetDrawable, this implementation returns the true width and height of the real image
- * that it's placeholding, instead of the intrinsic size of the contained drawable, so that
- * when used in an ImageView, it may be positioned/scaled/cropped the same way the real
- * image is.
- */
-public class PlaceholderInsetDrawable extends InsetDrawable {
- // The dimensions of the real image that this drawable is replacing.
- private final int mSourceWidth;
- private final int mSourceHeight;
-
- /**
- * Given a source drawable, wraps it around in this placeholder drawable by placing the
- * drawable at the center of the container if possible (or fill the container if the
- * drawable doesn't have intrinsic size such as color drawable).
- */
- public static PlaceholderInsetDrawable fromDrawable(final Drawable drawable,
- final int sourceWidth, final int sourceHeight) {
- final int drawableWidth = drawable.getIntrinsicWidth();
- final int drawableHeight = drawable.getIntrinsicHeight();
- final int insetHorizontal = drawableWidth < 0 || drawableWidth > sourceWidth ?
- 0 : (sourceWidth - drawableWidth) / 2;
- final int insetVertical = drawableHeight < 0 || drawableHeight > sourceHeight ?
- 0 : (sourceHeight - drawableHeight) / 2;
- return new PlaceholderInsetDrawable(drawable, insetHorizontal, insetVertical,
- insetHorizontal, insetVertical, sourceWidth, sourceHeight);
- }
-
- private PlaceholderInsetDrawable(final Drawable drawable, final int insetLeft,
- final int insetTop, final int insetRight, final int insetBottom,
- final int sourceWidth, final int sourceHeight) {
- super(drawable, insetLeft, insetTop, insetRight, insetBottom);
- mSourceWidth = sourceWidth;
- mSourceHeight = sourceHeight;
- }
-
- @Override
- public int getIntrinsicWidth() {
- return mSourceWidth;
- }
-
- @Override
- public int getIntrinsicHeight() {
- return mSourceHeight;
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/PlainTextEditText.java b/src/com/android/messaging/ui/PlainTextEditText.java
deleted file mode 100644
index 8d6e784..0000000
--- a/src/com/android/messaging/ui/PlainTextEditText.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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;
-
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.EditText;
-
-/**
- * We want the EditText used in Conversations to convert text to plain text on paste. This
- * conversion would happen anyway on send, so without this class it could appear to the user
- * that we would send e.g. bold or italic formatting, but in the sent message it would just be
- * plain text.
- */
-public class PlainTextEditText extends EditText {
- private static final char OBJECT_UNICODE = '\uFFFC';
-
- public PlainTextEditText(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- // Intercept and modify the paste event. Let everything else through unchanged.
- @Override
- public boolean onTextContextMenuItem(final int id) {
- if (id == android.R.id.paste) {
- // We can use this to know where the text position was originally before we pasted
- final int selectionStartPrePaste = getSelectionStart();
-
- // Let the EditText's normal paste routine fire, then modify the content after.
- // This is simpler than re-implementing the paste logic, which we'd have to do
- // if we want to get the text from the clipboard ourselves and then modify it.
-
- final boolean result = super.onTextContextMenuItem(id);
- CharSequence text = getText();
- int selectionStart = getSelectionStart();
- int selectionEnd = getSelectionEnd();
-
- // There is an option in the Chrome mobile app to copy image; however, instead of the
- // image in the form of the uri, Chrome gives us the html source for the image, which
- // the platform paste code turns into the unicode object character. The below section
- // of code looks for that edge case and replaces it with the url for the image.
- final int startIndex = selectionStart - 1;
- final int pasteStringLength = selectionStart - selectionStartPrePaste;
- // Only going to handle the case where the pasted object is the image
- if (pasteStringLength == 1 && text.charAt(startIndex) == OBJECT_UNICODE) {
- final ClipboardManager clipboard =
- (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
- final ClipData clip = clipboard.getPrimaryClip();
- if (clip != null) {
- ClipData.Item item = clip.getItemAt(0);
- StringBuilder sb = new StringBuilder(text);
- final String url = item.getText().toString();
- sb.replace(selectionStartPrePaste, selectionStart, url);
- text = sb.toString();
- selectionStart = selectionStartPrePaste + url.length();
- selectionEnd = selectionStart;
- }
- }
-
- // This removes the formatting due to the conversion to string.
- setText(text.toString(), BufferType.EDITABLE);
-
- // Restore the cursor selection state.
- setSelection(selectionStart, selectionEnd);
- return result;
- } else {
- return super.onTextContextMenuItem(id);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/PlaybackStateView.java b/src/com/android/messaging/ui/PlaybackStateView.java
deleted file mode 100644
index 8d9aac7..0000000
--- a/src/com/android/messaging/ui/PlaybackStateView.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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;
-
-/**
- * An interface for a UI element ("View") that reflects the playback state of a piece of media
- * content. It needs to support the ability to take common playback commands (play, pause, stop,
- * restart) and reflect the state in UI (through timer or progress bar etc.)
- */
-public interface PlaybackStateView {
- /**
- * Restart the playback.
- */
- void restart();
-
- /**
- * Reset ("stop") the playback to the starting position.
- */
- void reset();
-
- /**
- * Resume the playback, or start it if it hasn't been started yet.
- */
- void resume();
-
- /**
- * Pause the playback.
- */
- void pause();
-}
diff --git a/src/com/android/messaging/ui/RemoteInputEntrypointActivity.java b/src/com/android/messaging/ui/RemoteInputEntrypointActivity.java
deleted file mode 100644
index c731164..0000000
--- a/src/com/android/messaging/ui/RemoteInputEntrypointActivity.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.telephony.TelephonyManager;
-
-import com.android.messaging.datamodel.NoConfirmationSmsSendService;
-import com.android.messaging.util.LogUtil;
-
-public class RemoteInputEntrypointActivity extends BaseBugleActivity {
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- Intent intent = getIntent();
- if (intent == null) {
- LogUtil.w(TAG, "No intent attached");
- setResult(RESULT_CANCELED);
- finish();
- return;
- }
-
- // Perform some action depending on the intent
- String action = intent.getAction();
- if (Intent.ACTION_SENDTO.equals(action)) {
- // Build and send the intent
- final Intent sendIntent = new Intent(this, NoConfirmationSmsSendService.class);
- sendIntent.setAction(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE);
- sendIntent.putExtras(intent);
- // Wear apparently passes all of its extras via the clip data. Must pass it along.
- sendIntent.setClipData(intent.getClipData());
- startService(sendIntent);
- setResult(RESULT_OK);
- } else {
- LogUtil.w(TAG, "Unrecognized intent action: " + action);
- setResult(RESULT_CANCELED);
- }
- // This activity should never stick around after processing the intent
- finish();
- }
-}
diff --git a/src/com/android/messaging/ui/SmsStorageLowWarningActivity.java b/src/com/android/messaging/ui/SmsStorageLowWarningActivity.java
deleted file mode 100644
index 6b3e84b..0000000
--- a/src/com/android/messaging/ui/SmsStorageLowWarningActivity.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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;
-
-import android.app.FragmentTransaction;
-import android.os.Bundle;
-
-/**
- * Activity to contain the dialog of warning sms storage low.
- */
-public class SmsStorageLowWarningActivity extends BaseBugleFragmentActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- FragmentTransaction ft = getFragmentManager().beginTransaction();
- SmsStorageLowWarningFragment fragment =
- SmsStorageLowWarningFragment.newInstance();
- ft.add(fragment, null/*tag*/);
- ft.commit();
- }
-}
diff --git a/src/com/android/messaging/ui/SmsStorageLowWarningFragment.java b/src/com/android/messaging/ui/SmsStorageLowWarningFragment.java
deleted file mode 100644
index 3ebfdcf..0000000
--- a/src/com/android/messaging/ui/SmsStorageLowWarningFragment.java
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * 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;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.app.Fragment;
-import android.app.FragmentTransaction;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.action.HandleLowStorageAction;
-import com.android.messaging.sms.SmsReleaseStorage;
-import com.android.messaging.sms.SmsReleaseStorage.Duration;
-import com.android.messaging.sms.SmsStorageStatusManager;
-import com.android.messaging.util.Assert;
-import com.google.common.collect.Lists;
-
-import java.util.List;
-
-/**
- * Dialog to show the sms storage low warning
- */
-public class SmsStorageLowWarningFragment extends Fragment {
- private SmsStorageLowWarningFragment() {
- }
-
- public static SmsStorageLowWarningFragment newInstance() {
- return new SmsStorageLowWarningFragment();
- }
-
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- final FragmentTransaction ft = getFragmentManager().beginTransaction();
- final ChooseActionDialogFragment dialog = ChooseActionDialogFragment.newInstance();
- dialog.setTargetFragment(this, 0/*requestCode*/);
- dialog.show(ft, null/*tag*/);
- }
-
- /**
- * Perform confirm action for a specific action
- *
- * @param actionIndex
- */
- private void confirm(final int actionIndex) {
- final FragmentTransaction ft = getFragmentManager().beginTransaction();
- final ConfirmationDialog dialog = ConfirmationDialog.newInstance(actionIndex);
- dialog.setTargetFragment(this, 0/*requestCode*/);
- dialog.show(ft, null/*tag*/);
- }
-
- /**
- * The dialog is cancelled at any step
- */
- private void cancel() {
- getActivity().finish();
- }
-
- /**
- * The dialog to show for user to choose what delete actions to take when storage is low
- */
- private static class ChooseActionDialogFragment extends DialogFragment {
- public static ChooseActionDialogFragment newInstance() {
- return new ChooseActionDialogFragment();
- }
-
- @Override
- public Dialog onCreateDialog(final Bundle savedInstanceState) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
-
- final LayoutInflater inflater = getActivity().getLayoutInflater();
- final View dialogLayout = inflater.inflate(
- R.layout.sms_storage_low_warning_dialog, null);
- final ListView actionListView = (ListView) dialogLayout.findViewById(
- R.id.free_storage_action_list);
- final List<String> actions = loadFreeStorageActions(getActivity().getResources());
- final ActionListAdapter listAdapter = new ActionListAdapter(getActivity(), actions);
- actionListView.setAdapter(listAdapter);
-
- builder.setTitle(R.string.sms_storage_low_title)
- .setView(dialogLayout)
- .setNegativeButton(R.string.ignore, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- dialog.cancel();
- }
- });
-
- final Dialog dialog = builder.create();
- dialog.setCanceledOnTouchOutside(false);
- return dialog;
- }
-
- @Override
- public void onCancel(final DialogInterface dialog) {
- ((SmsStorageLowWarningFragment) getTargetFragment()).cancel();
- }
-
- private class ActionListAdapter extends ArrayAdapter<String> {
- public ActionListAdapter(final Context context, final List<String> actions) {
- super(context, R.layout.sms_free_storage_action_item_view, actions);
- }
-
- @Override
- public View getView(final int position, final View view, final ViewGroup parent) {
- TextView actionItemView;
- if (view == null || !(view instanceof TextView)) {
- final LayoutInflater inflater = LayoutInflater.from(getContext());
- actionItemView = (TextView) inflater.inflate(
- R.layout.sms_free_storage_action_item_view, parent, false);
- } else {
- actionItemView = (TextView) view;
- }
-
- final String action = getItem(position);
- actionItemView.setText(action);
- actionItemView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View view) {
- dismiss();
- ((SmsStorageLowWarningFragment) getTargetFragment()).confirm(position);
- }
- });
- return actionItemView;
- }
- }
- }
-
- private static final String KEY_ACTION_INDEX = "action_index";
-
- /**
- * The dialog to confirm user's delete action
- */
- private static class ConfirmationDialog extends DialogFragment {
- private Duration mDuration;
- private String mDurationString;
-
- public static ConfirmationDialog newInstance(final int actionIndex) {
- final ConfirmationDialog dialog = new ConfirmationDialog();
- final Bundle args = new Bundle();
- args.putInt(KEY_ACTION_INDEX, actionIndex);
- dialog.setArguments(args);
- return dialog;
- }
-
- @Override
- public void onCancel(final DialogInterface dialog) {
- ((SmsStorageLowWarningFragment) getTargetFragment()).cancel();
- }
-
- @Override
- public Dialog onCreateDialog(final Bundle savedInstanceState) {
- mDuration = SmsReleaseStorage.parseMessageRetainingDuration();
- mDurationString = SmsReleaseStorage.getMessageRetainingDurationString(mDuration);
-
- final int actionIndex = getArguments().getInt(KEY_ACTION_INDEX);
- if (actionIndex < 0 || actionIndex > 1) {
- return null;
- }
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setTitle(R.string.sms_storage_low_title)
- .setMessage(getConfirmDialogMessage(actionIndex))
- .setNegativeButton(android.R.string.cancel,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog,
- final int button) {
- dismiss();
- ((SmsStorageLowWarningFragment) getTargetFragment()).cancel();
- }
- })
- .setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog,
- final int button) {
- dismiss();
- handleAction(actionIndex);
- getActivity().finish();
- SmsStorageStatusManager.cancelStorageLowNotification();
- }
- });
- return builder.create();
- }
-
- private void handleAction(final int actionIndex) {
- final long durationInMillis =
- SmsReleaseStorage.durationToTimeInMillis(mDuration);
- switch (actionIndex) {
- case 0:
- HandleLowStorageAction.handleDeleteMediaMessages(durationInMillis);
- break;
-
- case 1:
- HandleLowStorageAction.handleDeleteOldMessages(durationInMillis);
- break;
-
- default:
- Assert.fail("Unsupported action");
- break;
- }
- }
-
- /**
- * Get the confirm dialog text for a specific delete action
- * @param index The action index
- * @return
- */
- private String getConfirmDialogMessage(final int index) {
- switch (index) {
- case 0:
- return getString(R.string.delete_all_media_confirmation, mDurationString);
- case 1:
- return getString(R.string.delete_oldest_messages_confirmation, mDurationString);
- case 2:
- return getString(R.string.auto_delete_oldest_messages_confirmation,
- mDurationString);
- }
- throw new IllegalArgumentException(
- "SmsStorageLowWarningFragment: invalid action index " + index);
- }
- }
-
- /**
- * Load the text of delete message actions
- *
- * @param resources
- * @return
- */
- private static List<String> loadFreeStorageActions(final Resources resources) {
- final Duration duration = SmsReleaseStorage.parseMessageRetainingDuration();
- final String durationString = SmsReleaseStorage.getMessageRetainingDurationString(duration);
- final List<String> actions = Lists.newArrayList();
- actions.add(resources.getString(R.string.delete_all_media));
- actions.add(resources.getString(R.string.delete_oldest_messages, durationString));
-
- // TODO: Auto-purging is disabled for Bugle V1.
- // actions.add(resources.getString(R.string.auto_delete_oldest_messages, durationString));
- return actions;
- }
-}
diff --git a/src/com/android/messaging/ui/SnackBar.java b/src/com/android/messaging/ui/SnackBar.java
deleted file mode 100644
index a278040..0000000
--- a/src/com/android/messaging/ui/SnackBar.java
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup.MarginLayoutParams;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.util.Assert;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class SnackBar {
- public static final int LONG_DURATION_IN_MS = 5000;
- public static final int SHORT_DURATION_IN_MS = 1000;
- public static final int MAX_DURATION_IN_MS = 10000;
-
- public interface SnackBarListener {
- void onActionClick();
- }
-
- /**
- * Defines an action to be performed when the user clicks on the action button on the snack bar
- */
- public static class Action {
- private final Runnable mActionRunnable;
- private final String mActionLabel;
-
- public final static int SNACK_BAR_UNDO = 0;
- public final static int SNACK_BAR_RETRY = 1;
-
- private Action(@Nullable Runnable actionRunnable, @Nullable String actionLabel) {
- mActionRunnable = actionRunnable;
- mActionLabel = actionLabel;
- }
-
- Runnable getActionRunnable() {
- return mActionRunnable;
- }
-
- String getActionLabel() {
- return mActionLabel;
- }
-
- public static Action createUndoAction(final Runnable undoRunnable) {
- return createCustomAction(undoRunnable, Factory.get().getApplicationContext()
- .getString(R.string.snack_bar_undo));
- }
-
- public static Action createRetryAction(final Runnable retryRunnable) {
- return createCustomAction(retryRunnable, Factory.get().getApplicationContext()
- .getString(R.string.snack_bar_retry));
- }
-
-
- public static Action createCustomAction(final Runnable runnable, final String actionLabel) {
- return new Action(runnable, actionLabel);
- }
- }
-
- /**
- * Defines the placement of the snack bar (e.g. anchored view, anchor gravity).
- */
- public static class Placement {
- private final View mAnchorView;
- private final boolean mAnchorAbove;
-
- private Placement(@NonNull final View anchorView, final boolean anchorAbove) {
- Assert.notNull(anchorView);
- mAnchorView = anchorView;
- mAnchorAbove = anchorAbove;
- }
-
- public View getAnchorView() {
- return mAnchorView;
- }
-
- public boolean getAnchorAbove() {
- return mAnchorAbove;
- }
-
- /**
- * Anchor the snack bar above the given {@code anchorView}.
- */
- public static Placement above(final View anchorView) {
- return new Placement(anchorView, true);
- }
-
- /**
- * Anchor the snack bar below the given {@code anchorView}.
- */
- public static Placement below(final View anchorView) {
- return new Placement(anchorView, false);
- }
- }
-
- public static class Builder {
- private static final List<SnackBarInteraction> NO_INTERACTIONS =
- new ArrayList<SnackBarInteraction>();
-
- private final Context mContext;
- private final SnackBarManager mSnackBarManager;
-
- private String mSnackBarMessage;
- private int mDuration = LONG_DURATION_IN_MS;
- private List<SnackBarInteraction> mInteractions = NO_INTERACTIONS;
- private Action mAction;
- private Placement mPlacement;
- // The parent view is only used to get a window token and doesn't affect the layout
- private View mParentView;
-
- public Builder(final SnackBarManager snackBarManager, final View parentView) {
- Assert.notNull(snackBarManager);
- Assert.notNull(parentView);
- mSnackBarManager = snackBarManager;
- mContext = parentView.getContext();
- mParentView = parentView;
- }
-
- public Builder setText(final String snackBarMessage) {
- Assert.isTrue(!TextUtils.isEmpty(snackBarMessage));
- mSnackBarMessage = snackBarMessage;
- return this;
- }
-
- public Builder setAction(final Action action) {
- mAction = action;
- return this;
- }
-
- /**
- * Sets the duration to show this toast for in milliseconds.
- */
- public Builder setDuration(final int duration) {
- Assert.isTrue(0 < duration && duration < MAX_DURATION_IN_MS);
- mDuration = duration;
- return this;
- }
-
- /**
- * Sets the components that this toast's animation will interact with. These components may
- * be animated to make room for the toast.
- */
- public Builder withInteractions(final List<SnackBarInteraction> interactions) {
- mInteractions = interactions;
- return this;
- }
-
- /**
- * Place the snack bar with the given placement requirement.
- */
- public Builder withPlacement(final Placement placement) {
- Assert.isNull(mPlacement);
- mPlacement = placement;
- return this;
- }
-
- public SnackBar build() {
- return new SnackBar(this);
- }
-
- public void show() {
- mSnackBarManager.show(build());
- }
- }
-
- private final View mRootView;
- private final Context mContext;
- private final View mSnackBarView;
- private final String mText;
- private final int mDuration;
- private final List<SnackBarInteraction> mInteractions;
- private final Action mAction;
- private final Placement mPlacement;
- private final TextView mActionTextView;
- private final TextView mMessageView;
- private final FrameLayout mMessageWrapper;
- private final View mParentView;
-
- private SnackBarListener mListener;
-
- private SnackBar(final Builder builder) {
- mContext = builder.mContext;
- mRootView = LayoutInflater.from(mContext).inflate(R.layout.snack_bar,
- null /* WindowManager will show this in show() below */);
- mSnackBarView = mRootView.findViewById(R.id.snack_bar);
- mText = builder.mSnackBarMessage;
- mDuration = builder.mDuration;
- mAction = builder.mAction;
- mPlacement = builder.mPlacement;
- mParentView = builder.mParentView;
- if (builder.mInteractions == null) {
- mInteractions = new ArrayList<SnackBarInteraction>();
- } else {
- mInteractions = builder.mInteractions;
- }
-
- mActionTextView = (TextView) mRootView.findViewById(R.id.snack_bar_action);
- mMessageView = (TextView) mRootView.findViewById(R.id.snack_bar_message);
- mMessageWrapper = (FrameLayout) mRootView.findViewById(R.id.snack_bar_message_wrapper);
-
- setUpButton();
- setUpTextLines();
- }
-
- public Context getContext() {
- return mContext;
- }
-
- public View getRootView() {
- return mRootView;
- }
-
- public View getParentView() {
- return mParentView;
- }
-
- public View getSnackBarView() {
- return mSnackBarView;
- }
-
- public String getMessageText() {
- return mText;
- }
-
- public String getActionLabel() {
- if (mAction == null) {
- return null;
- }
- return mAction.getActionLabel();
- }
-
- public int getDuration() {
- return mDuration;
- }
-
- public Placement getPlacement() {
- return mPlacement;
- }
-
- public List<SnackBarInteraction> getInteractions() {
- return mInteractions;
- }
-
- public void setEnabled(final boolean enabled) {
- mActionTextView.setClickable(enabled);
- }
-
- public void setListener(final SnackBarListener snackBarListener) {
- mListener = snackBarListener;
- }
-
- private void setUpButton() {
- if (mAction == null || mAction.getActionRunnable() == null) {
- mActionTextView.setVisibility(View.GONE);
- // In the XML layout we add left/right padding to the button to add space between
- // the message text and the button and on the right side we add padding to put space
- // between the button and the edge of the snack bar. This is so the button can use the
- // padding area as part of it's click target. Since we have no button, we need to put
- // some margin on the right side. While the left margin is already set on the wrapper,
- // we're setting it again to not have to make a special case for RTL.
- final MarginLayoutParams lp = (MarginLayoutParams) mMessageWrapper.getLayoutParams();
- final int leftRightMargin = mContext.getResources().getDimensionPixelSize(
- R.dimen.snack_bar_left_right_margin);
- lp.leftMargin = leftRightMargin;
- lp.rightMargin = leftRightMargin;
- mMessageWrapper.setLayoutParams(lp);
- } else {
- mActionTextView.setVisibility(View.VISIBLE);
- mActionTextView.setText(mAction.getActionLabel());
- mActionTextView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View v) {
- mAction.getActionRunnable().run();
- if (mListener != null) {
- mListener.onActionClick();
- }
- }
- });
- }
- }
-
- private void setUpTextLines() {
- if (mText == null) {
- mMessageView.setVisibility(View.GONE);
- } else {
- mMessageView.setVisibility(View.VISIBLE);
- mMessageView.setText(mText);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/SnackBarInteraction.java b/src/com/android/messaging/ui/SnackBarInteraction.java
deleted file mode 100644
index f723caa..0000000
--- a/src/com/android/messaging/ui/SnackBarInteraction.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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;
-
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewPropertyAnimator;
-
-import com.google.common.base.Preconditions;
-
-/**
- * An interface that defines how a component can be animated with an {@link SnackBar}.
- */
-public interface SnackBarInteraction {
- /**
- * Returns the animator that will be run in reaction to the given SnackBar being shown.
- *
- * Implementations may return null here if it determines that the given SnackBar does not need
- * to animate this component.
- */
- ViewPropertyAnimator animateOnSnackBarShow(SnackBar snackBar);
-
- /**
- * Returns the animator that will be run in reaction to the given SnackBar being dismissed.
- *
- * Implementations may return null here if it determines that the given SnackBar does not need
- * to animate this component.
- */
- ViewPropertyAnimator animateOnSnackBarDismiss(SnackBar snackBar);
-
- /**
- * A basic implementation of {@link SnackBarInteraction} that assumes that the
- * {@link SnackBar} is always shown with {@link Gravity#BOTTOM} and that the provided View will
- * always need to be translated up to make room for the SnackBar.
- */
- public static class BasicSnackBarInteraction implements SnackBarInteraction {
- private final View mView;
-
- public BasicSnackBarInteraction(final View view) {
- mView = Preconditions.checkNotNull(view);
- }
-
- @Override
- public ViewPropertyAnimator animateOnSnackBarShow(final SnackBar snackBar) {
- final View rootView = snackBar.getRootView();
- return mView.animate().translationY(-rootView.getMeasuredHeight());
- }
-
- @Override
- public ViewPropertyAnimator animateOnSnackBarDismiss(final SnackBar snackBar) {
- return mView.animate().translationY(0);
- }
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/SnackBarManager.java b/src/com/android/messaging/ui/SnackBarManager.java
deleted file mode 100644
index e107999..0000000
--- a/src/com/android/messaging/ui/SnackBarManager.java
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Handler;
-import android.text.TextUtils;
-import android.util.DisplayMetrics;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.View.OnTouchListener;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.view.ViewPropertyAnimator;
-import android.view.ViewTreeObserver.OnGlobalLayoutListener;
-import android.view.WindowManager;
-import android.widget.PopupWindow;
-import android.widget.PopupWindow.OnDismissListener;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.ui.SnackBar.Placement;
-import com.android.messaging.ui.SnackBar.SnackBarListener;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.TextUtil;
-import com.android.messaging.util.UiUtils;
-import com.google.common.base.Joiner;
-
-import java.util.List;
-
-public class SnackBarManager {
-
- private static SnackBarManager sInstance;
-
- public static SnackBarManager get() {
- if (sInstance == null) {
- synchronized (SnackBarManager.class) {
- if (sInstance == null) {
- sInstance = new SnackBarManager();
- }
- }
- }
- return sInstance;
- }
-
- private final Runnable mDismissRunnable = new Runnable() {
- @Override
- public void run() {
- dismiss();
- }
- };
-
- private final OnTouchListener mDismissOnTouchListener = new OnTouchListener() {
- @Override
- public boolean onTouch(final View view, final MotionEvent event) {
- // Dismiss the {@link SnackBar} but don't consume the event.
- dismiss();
- return false;
- }
- };
-
- private final SnackBarListener mDismissOnUserTapListener = new SnackBarListener() {
- @Override
- public void onActionClick() {
- dismiss();
- }
- };
-
- private final int mTranslationDurationMs;
- private final Handler mHideHandler;
-
- private SnackBar mCurrentSnackBar;
- private SnackBar mLatestSnackBar;
- private SnackBar mNextSnackBar;
- private boolean mIsCurrentlyDismissing;
- private PopupWindow mPopupWindow;
-
- private SnackBarManager() {
- mTranslationDurationMs = Factory.get().getApplicationContext().getResources().getInteger(
- R.integer.snackbar_translation_duration_ms);
- mHideHandler = new Handler();
- }
-
- public SnackBar getLatestSnackBar() {
- return mLatestSnackBar;
- }
-
- public SnackBar.Builder newBuilder(final View parentView) {
- return new SnackBar.Builder(this, parentView);
- }
-
- /**
- * The given snackBar is not guaranteed to be shown. If the previous snackBar is animating away,
- * and another snackBar is requested to show after this one, this snackBar will be skipped.
- */
- public void show(final SnackBar snackBar) {
- Assert.notNull(snackBar);
-
- if (mCurrentSnackBar != null) {
- LogUtil.d(LogUtil.BUGLE_TAG, "Showing snack bar, but currentSnackBar was not null.");
-
- // Dismiss the current snack bar. That will cause the next snack bar to be shown on
- // completion.
- mNextSnackBar = snackBar;
- mLatestSnackBar = snackBar;
- dismiss();
- return;
- }
-
- mCurrentSnackBar = snackBar;
- mLatestSnackBar = snackBar;
-
- // We want to know when either button was tapped so we can dismiss.
- snackBar.setListener(mDismissOnUserTapListener);
-
- // Cancel previous dismisses & set dismiss for the delay time.
- mHideHandler.removeCallbacks(mDismissRunnable);
- mHideHandler.postDelayed(mDismissRunnable, snackBar.getDuration());
-
- snackBar.setEnabled(false);
-
- // For some reason, the addView function does not respect layoutParams.
- // We need to explicitly set it first here.
- final View rootView = snackBar.getRootView();
-
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) {
- LogUtil.d(LogUtil.BUGLE_TAG, "Showing snack bar: " + snackBar);
- }
- // Measure the snack bar root view so we know how much to translate by.
- measureSnackBar(snackBar);
- mPopupWindow = new PopupWindow(snackBar.getContext());
- mPopupWindow.setWidth(LayoutParams.MATCH_PARENT);
- mPopupWindow.setHeight(LayoutParams.WRAP_CONTENT);
- mPopupWindow.setBackgroundDrawable(null);
- mPopupWindow.setContentView(rootView);
- final Placement placement = snackBar.getPlacement();
- if (placement == null) {
- mPopupWindow.showAtLocation(
- snackBar.getParentView(), Gravity.BOTTOM | Gravity.START,
- 0, getScreenBottomOffset(snackBar));
- } else {
- final View anchorView = placement.getAnchorView();
-
- // You'd expect PopupWindow.showAsDropDown to ensure the popup moves with the anchor
- // view, which it does for scrolling, but not layout changes, so we have to manually
- // update while the snackbar is showing
- final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- mPopupWindow.update(anchorView, 0, getRelativeOffset(snackBar),
- anchorView.getWidth(), LayoutParams.WRAP_CONTENT);
- }
- };
- anchorView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
- mPopupWindow.setOnDismissListener(new OnDismissListener() {
- @Override
- public void onDismiss() {
- anchorView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
- }
- });
- mPopupWindow.showAsDropDown(anchorView, 0, getRelativeOffset(snackBar));
- }
-
-
- // Animate the toast bar into view.
- placeSnackBarOffScreen(snackBar);
- animateSnackBarOnScreen(snackBar).withEndAction(new Runnable() {
- @Override
- public void run() {
- mCurrentSnackBar.setEnabled(true);
- makeCurrentSnackBarDismissibleOnTouch();
- // Fire an accessibility event as needed
- String snackBarText = snackBar.getMessageText();
- if (!TextUtils.isEmpty(snackBarText) &&
- TextUtils.getTrimmedLength(snackBarText) > 0) {
- snackBarText = snackBarText.trim();
- final String snackBarActionText = snackBar.getActionLabel();
- if (!TextUtil.isAllWhitespace(snackBarActionText)) {
- snackBarText = Joiner.on(", ").join(snackBarText, snackBarActionText);
- }
- AccessibilityUtil.announceForAccessibilityCompat(snackBar.getSnackBarView(),
- null /*accessibilityManager*/, snackBarText);
- }
- }
- });
-
- // Animate any interaction views out of the way.
- animateInteractionsOnShow(snackBar);
- }
-
- /**
- * Dismisses the current toast that is showing. If there is a toast waiting to be shown, that
- * toast will be shown when the current one has been dismissed.
- */
- public void dismiss() {
- mHideHandler.removeCallbacks(mDismissRunnable);
-
- if (mCurrentSnackBar == null || mIsCurrentlyDismissing) {
- return;
- }
-
- final SnackBar snackBar = mCurrentSnackBar;
-
- LogUtil.d(LogUtil.BUGLE_TAG, "Dismissing snack bar.");
- mIsCurrentlyDismissing = true;
-
- snackBar.setEnabled(false);
-
- // Animate the toast bar down.
- final View rootView = snackBar.getRootView();
- animateSnackBarOffScreen(snackBar).withEndAction(new Runnable() {
- @Override
- public void run() {
- rootView.setVisibility(View.GONE);
- try {
- mPopupWindow.dismiss();
- } catch (IllegalArgumentException e) {
- // PopupWindow.dismiss() will fire an IllegalArgumentException if the activity
- // has already ended while we were animating
- }
-
- mCurrentSnackBar = null;
- mIsCurrentlyDismissing = false;
-
- // Show the next toast if one is waiting.
- if (mNextSnackBar != null) {
- final SnackBar localNextSnackBar = mNextSnackBar;
- mNextSnackBar = null;
- show(localNextSnackBar);
- }
- }
- });
-
- // Animate any interaction views back.
- animateInteractionsOnDismiss(snackBar);
- }
-
- private void makeCurrentSnackBarDismissibleOnTouch() {
- // Set touching on the entire view, the {@link SnackBar} itself, as
- // well as the button's dismiss the toast.
- mCurrentSnackBar.getRootView().setOnTouchListener(mDismissOnTouchListener);
- mCurrentSnackBar.getSnackBarView().setOnTouchListener(mDismissOnTouchListener);
- }
-
- private void measureSnackBar(final SnackBar snackBar) {
- final View rootView = snackBar.getRootView();
- final Point displaySize = new Point();
- getWindowManager(snackBar.getContext()).getDefaultDisplay().getSize(displaySize);
- final int widthSpec = ViewGroup.getChildMeasureSpec(
- MeasureSpec.makeMeasureSpec(displaySize.x, MeasureSpec.EXACTLY),
- 0, LayoutParams.MATCH_PARENT);
- final int heightSpec = ViewGroup.getChildMeasureSpec(
- MeasureSpec.makeMeasureSpec(displaySize.y, MeasureSpec.EXACTLY),
- 0, LayoutParams.WRAP_CONTENT);
- rootView.measure(widthSpec, heightSpec);
- }
-
- private void placeSnackBarOffScreen(final SnackBar snackBar) {
- final View rootView = snackBar.getRootView();
- final View snackBarView = snackBar.getSnackBarView();
- snackBarView.setTranslationY(rootView.getMeasuredHeight());
- }
-
- private ViewPropertyAnimator animateSnackBarOnScreen(final SnackBar snackBar) {
- final View snackBarView = snackBar.getSnackBarView();
- return normalizeAnimator(snackBarView.animate()).translationX(0).translationY(0);
- }
-
- private ViewPropertyAnimator animateSnackBarOffScreen(final SnackBar snackBar) {
- final View rootView = snackBar.getRootView();
- final View snackBarView = snackBar.getSnackBarView();
- return normalizeAnimator(snackBarView.animate()).translationY(rootView.getHeight());
- }
-
- private void animateInteractionsOnShow(final SnackBar snackBar) {
- final List<SnackBarInteraction> interactions = snackBar.getInteractions();
- for (final SnackBarInteraction interaction : interactions) {
- if (interaction != null) {
- final ViewPropertyAnimator animator = interaction.animateOnSnackBarShow(snackBar);
- if (animator != null) {
- normalizeAnimator(animator);
- }
- }
- }
- }
-
- private void animateInteractionsOnDismiss(final SnackBar snackBar) {
- final List<SnackBarInteraction> interactions = snackBar.getInteractions();
- for (final SnackBarInteraction interaction : interactions) {
- if (interaction != null) {
- final ViewPropertyAnimator animator =
- interaction.animateOnSnackBarDismiss(snackBar);
- if (animator != null) {
- normalizeAnimator(animator);
- }
- }
- }
- }
-
- private ViewPropertyAnimator normalizeAnimator(final ViewPropertyAnimator animator) {
- return animator
- .setInterpolator(UiUtils.DEFAULT_INTERPOLATOR)
- .setDuration(mTranslationDurationMs);
- }
-
- private WindowManager getWindowManager(final Context context) {
- return (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- }
-
- /**
- * Get the offset from the bottom of the screen where the snack bar should be placed.
- */
- private int getScreenBottomOffset(final SnackBar snackBar) {
- final WindowManager windowManager = getWindowManager(snackBar.getContext());
- final DisplayMetrics displayMetrics = new DisplayMetrics();
- if (OsUtil.isAtLeastL()) {
- windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
- } else {
- windowManager.getDefaultDisplay().getMetrics(displayMetrics);
- }
- final int screenHeight = displayMetrics.heightPixels;
-
- if (OsUtil.isAtLeastL()) {
- // In L, the navigation bar is included in the space for the popup window, so we have to
- // offset by the size of the navigation bar
- final Rect displayRect = new Rect();
- snackBar.getParentView().getRootView().getWindowVisibleDisplayFrame(displayRect);
- return screenHeight - displayRect.bottom;
- }
-
- return 0;
- }
-
- private int getRelativeOffset(final SnackBar snackBar) {
- final Placement placement = snackBar.getPlacement();
- Assert.notNull(placement);
- final View anchorView = placement.getAnchorView();
- if (placement.getAnchorAbove()) {
- return -snackBar.getRootView().getMeasuredHeight() - anchorView.getHeight();
- } else {
- // Use the default dropdown positioning
- return 0;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/TestActivity.java b/src/com/android/messaging/ui/TestActivity.java
deleted file mode 100644
index 1693660..0000000
--- a/src/com/android/messaging/ui/TestActivity.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import android.support.v4.app.FragmentActivity;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-
-import com.android.messaging.R;
-import com.android.messaging.util.LogUtil;
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * An empty activity that can be used to host a fragment or view for unit testing purposes. Lives in
- * app code vs test code due to requirement of ActivityInstrumentationTestCase2.
- */
-public class TestActivity extends FragmentActivity {
- private FragmentEventListener mFragmentEventListener;
-
- public interface FragmentEventListener {
- public void onAttachFragment(Fragment fragment);
- }
-
- @Override
- protected void onCreate(final Bundle bundle) {
- super.onCreate(bundle);
-
- if (bundle != null) {
- // The test case may have configured the fragment, and recreating the activity will
- // lose that configuration. The real activity is the only activity that would know
- // how to reapply that configuration.
- throw new IllegalStateException("TestActivity cannot get recreated");
- }
-
- // There is a race condition, but this often makes it possible for tests to run with the
- // key guard up
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
- WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
- WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
- WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
-
- setContentView(R.layout.test_activity);
- }
-
- @VisibleForTesting
- public void setFragment(final Fragment fragment) {
- LogUtil.i(LogUtil.BUGLE_TAG, "TestActivity.setFragment");
- getFragmentManager()
- .beginTransaction()
- .replace(R.id.test_content, fragment)
- .commit();
- getFragmentManager().executePendingTransactions();
- }
-
- @VisibleForTesting
- public void setView(final View view) {
- LogUtil.i(LogUtil.BUGLE_TAG, "TestActivity.setView");
- ((FrameLayout) findViewById(R.id.test_content)).addView(view);
- }
-
- @Override
- public void onAttachFragment(final Fragment fragment) {
- if (mFragmentEventListener != null) {
- mFragmentEventListener.onAttachFragment(fragment);
- }
- }
-
- public void setFragmentEventListener(final FragmentEventListener fragmentEventListener) {
- mFragmentEventListener = fragmentEventListener;
- }
-}
diff --git a/src/com/android/messaging/ui/UIIntents.java b/src/com/android/messaging/ui/UIIntents.java
deleted file mode 100644
index e5f8a52..0000000
--- a/src/com/android/messaging/ui/UIIntents.java
+++ /dev/null
@@ -1,378 +0,0 @@
-/*
- * 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;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.PendingIntent;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Bundle;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.util.ConversationIdSet;
-
-/**
- * A central repository of Intents used to start activities.
- */
-public abstract class UIIntents {
- public static UIIntents get() {
- return Factory.get().getUIIntents();
- }
-
- // Intent extras
- public static final String UI_INTENT_EXTRA_CONVERSATION_ID = "conversation_id";
-
- // Sending draft data (from share intent / message forwarding) to the ConversationActivity.
- public static final String UI_INTENT_EXTRA_DRAFT_DATA = "draft_data";
-
- // The request code for picking image from the Document picker.
- public static final int REQUEST_PICK_IMAGE_FROM_DOCUMENT_PICKER = 1400;
-
- // Indicates what type of notification this applies to (See BugleNotifications:
- // UPDATE_NONE, UPDATE_MESSAGES, UPDATE_ERRORS, UPDATE_ALL)
- public static final String UI_INTENT_EXTRA_NOTIFICATIONS_UPDATE = "notifications_update";
-
- // Pass a set of conversation id's.
- public static final String UI_INTENT_EXTRA_CONVERSATION_ID_SET = "conversation_id_set";
-
- // Sending class zero message to its activity
- public static final String UI_INTENT_EXTRA_MESSAGE_VALUES = "message_values";
-
- // For the widget to go to the ConversationList from the Conversation.
- public static final String UI_INTENT_EXTRA_GOTO_CONVERSATION_LIST = "goto_conv_list";
-
- // Indicates whether a conversation is launched with custom transition.
- public static final String UI_INTENT_EXTRA_WITH_CUSTOM_TRANSITION = "with_custom_transition";
-
- public static final String ACTION_RESET_NOTIFICATIONS =
- "com.android.messaging.reset_notifications";
-
- // Sending VCard uri to VCard detail activity
- public static final String UI_INTENT_EXTRA_VCARD_URI = "vcard_uri";
-
- public static final String CMAS_COMPONENT = "com.android.cellbroadcastreceiver";
-
- // Intent action for local broadcast receiver for conversation self id change.
- public static final String CONVERSATION_SELF_ID_CHANGE_BROADCAST_ACTION =
- "conversation_self_id_change";
-
- // Conversation self id
- public static final String UI_INTENT_EXTRA_CONVERSATION_SELF_ID = "conversation_self_id";
-
- // For opening an APN editor on a particular row in the apn database.
- public static final String UI_INTENT_EXTRA_APN_ROW_ID = "apn_row_id";
-
- // Subscription id
- public static final String UI_INTENT_EXTRA_SUB_ID = "sub_id";
-
- // Per-Subscription setting activity title
- public static final String UI_INTENT_EXTRA_PER_SUBSCRIPTION_SETTING_TITLE =
- "per_sub_setting_title";
-
- // Is application settings launched as the top level settings activity?
- public static final String UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS = "top_level_settings";
-
- // Sending attachment uri from widget
- public static final String UI_INTENT_EXTRA_ATTACHMENT_URI = "attachment_uri";
-
- // Sending attachment content type from widget
- public static final String UI_INTENT_EXTRA_ATTACHMENT_TYPE = "attachment_type";
-
- public static final String ACTION_WIDGET_CONVERSATION =
- "com.android.messaging.widget_conversation:";
-
- public static final String UI_INTENT_EXTRA_REQUIRES_MMS = "requires_mms";
-
- public static final String UI_INTENT_EXTRA_SELF_ID = "self_id";
-
- // Message position to scroll to.
- public static final String UI_INTENT_EXTRA_MESSAGE_POSITION = "message_position";
-
- /**
- * Launch the permission check activity
- */
- public abstract void launchPermissionCheckActivity(final Context context);
-
- public abstract void launchConversationListActivity(final Context context);
-
- /**
- * Launch an activity to show a conversation. This method by default provides no additional
- * activity options.
- */
- public void launchConversationActivity(final Context context,
- final String conversationId, final MessageData draft) {
- launchConversationActivity(context, conversationId, draft, null,
- false /* withCustomTransition */);
- }
-
- /**
- * Launch an activity to show a conversation.
- */
- public abstract void launchConversationActivity(final Context context,
- final String conversationId, final MessageData draft, final Bundle activityOptions,
- final boolean withCustomTransition);
-
-
- /**
- * Launch an activity to show conversation with conversation list in back stack.
- */
- public abstract void launchConversationActivityWithParentStack(Context context,
- String conversationId, String smsBody);
-
- /**
- * Launch an activity to show a conversation as a new task.
- */
- public abstract void launchConversationActivityNewTask(final Context context,
- final String conversationId);
-
- /**
- * Launch an activity to start a new conversation
- */
- public abstract void launchCreateNewConversationActivity(final Context context,
- final MessageData draft);
-
- /**
- * Launch debug activity to set MMS config options.
- */
- public abstract void launchDebugMmsConfigActivity(final Context context);
-
- /**
- * Launch an activity to change settings.
- */
- public abstract void launchSettingsActivity(final Context context);
-
- /**
- * Launch an activity to add a contact with a given destination.
- */
- public abstract void launchAddContactActivity(final Context context, final String destination);
-
- /**
- * Launch an activity to show the document picker to pick an image.
- * @param fragment the requesting fragment
- */
- public abstract void launchDocumentImagePicker(final Fragment fragment);
-
- /**
- * Launch an activity to show people & options for a given conversation.
- */
- public abstract void launchPeopleAndOptionsActivity(final Activity context,
- final String conversationId);
-
- /**
- * Launch an external activity to handle a phone call
- * @param phoneNumber the phone number to call
- * @param clickPosition is the location tapped to start this launch for transition use
- */
- public abstract void launchPhoneCallActivity(final Context context, final String phoneNumber,
- final Point clickPosition);
-
- /**
- * Launch an activity to show archived conversations.
- */
- public abstract void launchArchivedConversationsActivity(final Context context);
-
- /**
- * Launch an activity to show blocked participants.
- */
- public abstract void launchBlockedParticipantsActivity(final Context context);
-
- /**
- * Launch an activity to show a class zero message
- */
- public abstract void launchClassZeroActivity(Context context, ContentValues messageValues);
-
- /**
- * Launch an activity to let the user forward a message
- */
- public abstract void launchForwardMessageActivity(Context context, MessageData message);
-
- /**
- * Launch an activity to show details for a VCard
- */
- public abstract void launchVCardDetailActivity(Context context, Uri vcardUri);
-
- /**
- * Launch an external activity that handles the intent to add VCard to contacts
- */
- public abstract void launchSaveVCardToContactsActivity(Context context, Uri vcardUri);
-
- /**
- * Launch an activity to let the user select & unselect the list of attachments to send.
- */
- public abstract void launchAttachmentChooserActivity(final Activity activity,
- final String conversationId, final int requestCode);
-
- /**
- * Launch full screen video viewer.
- */
- public abstract void launchFullScreenVideoViewer(Context context, Uri videoUri);
-
- /**
- * Launch full screen photo viewer.
- */
- public abstract void launchFullScreenPhotoViewer(Activity activity, Uri initialPhoto,
- Rect initialPhotoBounds, Uri photosUri);
-
- /**
- * Launch an activity to show general app settings
- * @param topLevel indicates whether the app settings is launched as the top-level settings
- * activity (instead of SettingsActivity which shows a collapsed view of the app
- * settings + one settings item per subscription). This is true when there's only one
- * active SIM in the system so we can show this activity directly.
- */
- public abstract void launchApplicationSettingsActivity(Context context, boolean topLevel);
-
- /**
- * Launch an activity to show per-subscription settings
- */
- public abstract void launchPerSubscriptionSettingsActivity(Context context, int subId,
- String settingTitle);
-
- /**
- * Get a ACTION_VIEW intent
- * @param url display the data in the url to users
- */
- public abstract Intent getViewUrlIntent(final String url);
-
- /**
- * Get an intent to launch the ringtone picker
- * @param title the title to show in the ringtone picker
- * @param existingUri the currently set uri
- * @param defaultUri the default uri if none is currently set
- * @param toneType type of ringtone to pick, maybe any of RingtoneManager.TYPE_*
- */
- public abstract Intent getRingtonePickerIntent(final String title, final Uri existingUri,
- final Uri defaultUri, final int toneType);
-
- /**
- * Get an intent to launch the wireless alert viewer.
- */
- public abstract Intent getWirelessAlertsIntent();
-
- /**
- * Get an intent to launch the dialog for changing the default SMS App.
- */
- public abstract Intent getChangeDefaultSmsAppIntent(final Activity activity);
-
- /**
- * Broadcast conversation self id change so it may be reflected in the message compose UI.
- */
- public abstract void broadcastConversationSelfIdChange(final Context context,
- final String conversationId, final String conversationSelfId);
-
- /**
- * Get a PendingIntent for starting conversation list from notifications.
- */
- public abstract PendingIntent getPendingIntentForConversationListActivity(
- final Context context);
-
- /**
- * Get a PendingIntent for starting conversation list from widget.
- */
- public abstract PendingIntent getWidgetPendingIntentForConversationListActivity(
- final Context context);
-
- /**
- * Get a PendingIntent for showing a conversation from notifications.
- */
- public abstract PendingIntent getPendingIntentForConversationActivity(final Context context,
- final String conversationId, final MessageData draft);
-
- /**
- * Get an Intent for showing a conversation from the widget.
- */
- public abstract Intent getIntentForConversationActivity(final Context context,
- final String conversationId, final MessageData draft);
-
- /**
- * Get a PendingIntent for sending a message to a conversation, without opening the Bugle UI.
- *
- * <p>This is intended to be used by the Android Wear companion app when sending transcribed
- * voice replies.
- */
- public abstract PendingIntent getPendingIntentForSendingMessageToConversation(
- final Context context, final String conversationId, final String selfId,
- final boolean requiresMms, final int requestCode);
-
- /**
- * Get a PendingIntent for clearing notifications.
- *
- * <p>This is intended to be used by notifications.
- */
- public abstract PendingIntent getPendingIntentForClearingNotifications(final Context context,
- final int updateTargets, final ConversationIdSet conversationIdSet,
- final int requestCode);
-
- /**
- * Get a PendingIntent for showing low storage notifications.
- */
- public abstract PendingIntent getPendingIntentForLowStorageNotifications(final Context context);
-
- /**
- * Get a PendingIntent for showing a new message to a secondary user.
- */
- public abstract PendingIntent getPendingIntentForSecondaryUserNewMessageNotification(
- final Context context);
-
- /**
- * Get an intent for showing the APN editor.
- */
- public abstract Intent getApnEditorIntent(final Context context, final String rowId, int subId);
-
- /**
- * Get an intent for showing the APN settings.
- */
- public abstract Intent getApnSettingsIntent(final Context context, final int subId);
-
- /**
- * Get an intent for showing advanced settings.
- */
- public abstract Intent getAdvancedSettingsIntent(final Context context);
-
- /**
- * Get an intent for the LaunchConversationActivity.
- */
- public abstract Intent getLaunchConversationActivityIntent(final Context context);
-
- /**
- * Tell MediaScanner to re-scan the specified volume.
- */
- public abstract void kickMediaScanner(final Context context, final String volume);
-
- /**
- * Launch to browser for a url.
- */
- public abstract void launchBrowserForUrl(final Context context, final String url);
-
- /**
- * Get a PendingIntent for the widget conversation template.
- */
- public abstract PendingIntent getWidgetPendingIntentForConversationActivity(
- final Context context, final String conversationId, final int requestCode);
-
- /**
- * Get a PendingIntent for the conversation widget configuration activity template.
- */
- public abstract PendingIntent getWidgetPendingIntentForConfigurationActivity(
- final Context context, final int appWidgetId);
-
-}
diff --git a/src/com/android/messaging/ui/UIIntentsImpl.java b/src/com/android/messaging/ui/UIIntentsImpl.java
deleted file mode 100644
index b7db719..0000000
--- a/src/com/android/messaging/ui/UIIntentsImpl.java
+++ /dev/null
@@ -1,577 +0,0 @@
-/*
- * 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;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.app.PendingIntent;
-import android.appwidget.AppWidgetManager;
-import android.content.ActivityNotFoundException;
-import android.content.ClipData;
-import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Intents;
-import android.provider.MediaStore;
-import android.provider.Telephony;
-import android.support.annotation.Nullable;
-import android.support.v4.app.TaskStackBuilder;
-import android.support.v4.content.LocalBroadcastManager;
-import android.text.TextUtils;
-
-import com.android.ex.photo.Intents.PhotoViewIntentBuilder;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.ConversationImagePartsView;
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.receiver.NotificationReceiver;
-import com.android.messaging.sms.MmsSmsUtils;
-import com.android.messaging.ui.appsettings.ApnEditorActivity;
-import com.android.messaging.ui.appsettings.ApnSettingsActivity;
-import com.android.messaging.ui.appsettings.ApplicationSettingsActivity;
-import com.android.messaging.ui.appsettings.PerSubscriptionSettingsActivity;
-import com.android.messaging.ui.appsettings.SettingsActivity;
-import com.android.messaging.ui.attachmentchooser.AttachmentChooserActivity;
-import com.android.messaging.ui.conversation.ConversationActivity;
-import com.android.messaging.ui.conversation.LaunchConversationActivity;
-import com.android.messaging.ui.conversationlist.ArchivedConversationListActivity;
-import com.android.messaging.ui.conversationlist.ConversationListActivity;
-import com.android.messaging.ui.conversationlist.ForwardMessageActivity;
-import com.android.messaging.ui.conversationsettings.PeopleAndOptionsActivity;
-import com.android.messaging.ui.debug.DebugMmsConfigActivity;
-import com.android.messaging.ui.photoviewer.BuglePhotoViewActivity;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.ConversationIdSet;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.UiUtils;
-import com.android.messaging.util.UriUtil;
-
-/**
- * A central repository of Intents used to start activities.
- */
-public class UIIntentsImpl extends UIIntents {
- private static final String CELL_BROADCAST_LIST_ACTIVITY =
- "com.android.cellbroadcastreceiver.CellBroadcastListActivity";
- private static final String CALL_TARGET_CLICK_KEY = "touchPoint";
- private static final String CALL_TARGET_CLICK_EXTRA_KEY =
- "android.telecom.extra.OUTGOING_CALL_EXTRAS";
- private static final String MEDIA_SCANNER_CLASS =
- "com.android.providers.media.MediaScannerService";
- private static final String MEDIA_SCANNER_PACKAGE = "com.android.providers.media";
- private static final String MEDIA_SCANNER_SCAN_ACTION = "android.media.IMediaScannerService";
-
- /**
- * Get an intent which takes you to a conversation
- */
- private Intent getConversationActivityIntent(final Context context,
- final String conversationId, final MessageData draft,
- final boolean withCustomTransition) {
- final Intent intent = new Intent(context, ConversationActivity.class);
-
- // Always try to reuse the same ConversationActivity in the current task so that we don't
- // have two conversation activities in the back stack.
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-
- // Otherwise we're starting a new conversation
- if (conversationId != null) {
- intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
- }
- if (draft != null) {
- intent.putExtra(UI_INTENT_EXTRA_DRAFT_DATA, draft);
-
- // If draft attachments came from an external content provider via a share intent, we
- // need to propagate the URI permissions through to ConversationActivity. This requires
- // putting the URIs into the ClipData (setData also works, but accepts only one URI).
- ClipData clipData = null;
- for (final MessagePartData partData : draft.getParts()) {
- if (partData.isAttachment()) {
- final Uri uri = partData.getContentUri();
- if (clipData == null) {
- clipData = ClipData.newRawUri("Attachments", uri);
- } else {
- clipData.addItem(new ClipData.Item(uri));
- }
- }
- }
- if (clipData != null) {
- intent.setClipData(clipData);
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- }
- }
- if (withCustomTransition) {
- intent.putExtra(UI_INTENT_EXTRA_WITH_CUSTOM_TRANSITION, true);
- }
-
- if (!(context instanceof Activity)) {
- // If the caller supplies an application context, and not an activity context, we must
- // include this flag
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- }
- return intent;
- }
-
- @Override
- public void launchPermissionCheckActivity(final Context context) {
- final Intent intent = new Intent(context, PermissionCheckActivity.class);
- context.startActivity(intent);
- }
-
- /**
- * Get an intent which takes you to the conversation list
- */
- private Intent getConversationListActivityIntent(final Context context) {
- return new Intent(context, ConversationListActivity.class);
- }
-
- @Override
- public void launchConversationListActivity(final Context context) {
- final Intent intent = getConversationListActivityIntent(context);
- context.startActivity(intent);
- }
-
- /**
- * Get an intent which shows the low storage warning activity.
- */
- private Intent getSmsStorageLowWarningActivityIntent(final Context context) {
- return new Intent(context, SmsStorageLowWarningActivity.class);
- }
-
- @Override
- public void launchConversationActivity(final Context context,
- final String conversationId, final MessageData draft, final Bundle activityOptions,
- final boolean withCustomTransition) {
- Assert.isTrue(!withCustomTransition || activityOptions != null);
- final Intent intent = getConversationActivityIntent(context, conversationId, draft,
- withCustomTransition);
- context.startActivity(intent, activityOptions);
- }
-
- @Override
- public void launchConversationActivityNewTask(
- final Context context, final String conversationId) {
- final Intent intent = getConversationActivityIntent(context, conversationId, null,
- false /* withCustomTransition */);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- context.startActivity(intent);
- }
-
- @Override
- public void launchConversationActivityWithParentStack(final Context context,
- final String conversationId, final String smsBody) {
- final MessageData messageData = TextUtils.isEmpty(smsBody)
- ? null
- : MessageData.createDraftSmsMessage(conversationId, null, smsBody);
- TaskStackBuilder.create(context)
- .addNextIntentWithParentStack(
- getConversationActivityIntent(context, conversationId, messageData,
- false /* withCustomTransition */))
- .startActivities();
- }
-
- @Override
- public void launchCreateNewConversationActivity(final Context context,
- final MessageData draft) {
- final Intent intent = getConversationActivityIntent(context, null, draft,
- false /* withCustomTransition */);
- context.startActivity(intent);
- }
-
- @Override
- public void launchDebugMmsConfigActivity(final Context context) {
- context.startActivity(new Intent(context, DebugMmsConfigActivity.class));
- }
-
- @Override
- public void launchAddContactActivity(final Context context, final String destination) {
- final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
- final String destinationType = MmsSmsUtils.isEmailAddress(destination) ?
- Intents.Insert.EMAIL : Intents.Insert.PHONE;
- intent.setType(Contacts.CONTENT_ITEM_TYPE);
- intent.putExtra(destinationType, destination);
- startExternalActivity(context, intent);
- }
-
- @Override
- public void launchSettingsActivity(final Context context) {
- final Intent intent = new Intent(context, SettingsActivity.class);
- context.startActivity(intent);
- }
-
- @Override
- public void launchArchivedConversationsActivity(final Context context) {
- final Intent intent = new Intent(context, ArchivedConversationListActivity.class);
- context.startActivity(intent);
- }
-
- @Override
- public void launchBlockedParticipantsActivity(final Context context) {
- final Intent intent = new Intent(context, BlockedParticipantsActivity.class);
- context.startActivity(intent);
- }
-
- @Override
- public void launchDocumentImagePicker(final Fragment fragment) {
- final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.putExtra(Intent.EXTRA_MIME_TYPES, MessagePartData.ACCEPTABLE_IMAGE_TYPES);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType(ContentType.IMAGE_UNSPECIFIED);
-
- fragment.startActivityForResult(intent, REQUEST_PICK_IMAGE_FROM_DOCUMENT_PICKER);
- }
-
- @Override
- public void launchPeopleAndOptionsActivity(final Activity activity,
- final String conversationId) {
- final Intent intent = new Intent(activity, PeopleAndOptionsActivity.class);
- intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
- activity.startActivityForResult(intent, 0);
- }
-
- @Override
- public void launchPhoneCallActivity(final Context context, final String phoneNumber,
- final Point clickPosition) {
- final Intent intent = new Intent(Intent.ACTION_CALL,
- Uri.parse(UriUtil.SCHEME_TEL + phoneNumber));
- final Bundle extras = new Bundle();
- extras.putParcelable(CALL_TARGET_CLICK_KEY, clickPosition);
- intent.putExtra(CALL_TARGET_CLICK_EXTRA_KEY, extras);
- startExternalActivity(context, intent);
- }
-
- @Override
- public void launchClassZeroActivity(final Context context, final ContentValues messageValues) {
- final Intent classZeroIntent = new Intent(context, ClassZeroActivity.class)
- .putExtra(UI_INTENT_EXTRA_MESSAGE_VALUES, messageValues)
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
- context.startActivity(classZeroIntent);
- }
-
- @Override
- public void launchForwardMessageActivity(final Context context, final MessageData message) {
- final Intent forwardMessageIntent = new Intent(context, ForwardMessageActivity.class)
- .putExtra(UI_INTENT_EXTRA_DRAFT_DATA, message);
- context.startActivity(forwardMessageIntent);
- }
-
- @Override
- public void launchVCardDetailActivity(final Context context, final Uri vcardUri) {
- final Intent vcardDetailIntent = new Intent(context, VCardDetailActivity.class)
- .putExtra(UI_INTENT_EXTRA_VCARD_URI, vcardUri);
- context.startActivity(vcardDetailIntent);
- }
-
- @Override
- public void launchSaveVCardToContactsActivity(final Context context, final Uri vcardUri) {
- Assert.isTrue(MediaScratchFileProvider.isMediaScratchSpaceUri(vcardUri));
- final Intent intent = new Intent();
- intent.setAction(Intent.ACTION_VIEW);
- intent.setDataAndType(vcardUri, ContentType.TEXT_VCARD.toLowerCase());
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- startExternalActivity(context, intent);
- }
-
- @Override
- public void launchAttachmentChooserActivity(final Activity activity,
- final String conversationId, final int requestCode) {
- final Intent intent = new Intent(activity, AttachmentChooserActivity.class);
- intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
- activity.startActivityForResult(intent, requestCode);
- }
-
- @Override
- public void launchFullScreenVideoViewer(final Context context, final Uri videoUri) {
- final Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- // So we don't see "surrounding" images in Gallery
- intent.putExtra("SingleItemOnly", true);
- intent.setDataAndType(videoUri, ContentType.VIDEO_UNSPECIFIED);
- startExternalActivity(context, intent);
- }
-
- @Override
- public void launchFullScreenPhotoViewer(final Activity activity, final Uri initialPhoto,
- final Rect initialPhotoBounds, final Uri photosUri) {
- final PhotoViewIntentBuilder builder =
- com.android.ex.photo.Intents.newPhotoViewIntentBuilder(
- activity, BuglePhotoViewActivity.class);
- builder.setPhotosUri(photosUri.toString());
- builder.setInitialPhotoUri(initialPhoto.toString());
- builder.setProjection(ConversationImagePartsView.PhotoViewQuery.PROJECTION);
-
- // Set the location of the imageView so that the photoviewer can animate from that location
- // to full screen.
- builder.setScaleAnimation(initialPhotoBounds.left, initialPhotoBounds.top,
- initialPhotoBounds.width(), initialPhotoBounds.height());
-
- builder.setDisplayThumbsFullScreen(false);
- builder.setMaxInitialScale(8);
- activity.startActivity(builder.build());
- activity.overridePendingTransition(0, 0);
- }
-
- @Override
- public void launchApplicationSettingsActivity(final Context context, final boolean topLevel) {
- final Intent intent = new Intent(context, ApplicationSettingsActivity.class);
- intent.putExtra(UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS, topLevel);
- context.startActivity(intent);
- }
-
- @Override
- public void launchPerSubscriptionSettingsActivity(final Context context, final int subId,
- final String settingTitle) {
- final Intent intent = getPerSubscriptionSettingsIntent(context, subId, settingTitle);
- context.startActivity(intent);
- }
-
- @Override
- public Intent getViewUrlIntent(final String url) {
- final Uri uri = Uri.parse(url);
- return new Intent(Intent.ACTION_VIEW, uri);
- }
-
- @Override
- public void broadcastConversationSelfIdChange(final Context context,
- final String conversationId, final String conversationSelfId) {
- final Intent intent = new Intent(CONVERSATION_SELF_ID_CHANGE_BROADCAST_ACTION);
- intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
- intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_SELF_ID, conversationSelfId);
- LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
- }
-
- @Override
- public PendingIntent getPendingIntentForConversationListActivity(final Context context) {
- final Intent intent = getConversationListActivityIntent(context);
- return getPendingIntentWithParentStack(context, intent, 0);
- }
-
- @Override
- public PendingIntent getPendingIntentForConversationActivity(final Context context,
- final String conversationId, final MessageData draft) {
- final Intent intent = getConversationActivityIntent(context, conversationId, draft,
- false /* withCustomTransition */);
- // Ensure that the platform doesn't reuse PendingIntents across conversations
- intent.setData(MessagingContentProvider.buildConversationMetadataUri(conversationId));
- return getPendingIntentWithParentStack(context, intent, 0);
- }
-
- @Override
- public Intent getIntentForConversationActivity(final Context context,
- final String conversationId, final MessageData draft) {
- final Intent intent = getConversationActivityIntent(context, conversationId, draft,
- false /* withCustomTransition */);
- return intent;
- }
-
- @Override
- public PendingIntent getPendingIntentForSendingMessageToConversation(final Context context,
- final String conversationId, final String selfId, final boolean requiresMms,
- final int requestCode) {
- final Intent intent = new Intent(context, RemoteInputEntrypointActivity.class);
- intent.setAction(Intent.ACTION_SENDTO);
- // Ensure that the platform doesn't reuse PendingIntents across conversations
- intent.setData(MessagingContentProvider.buildConversationMetadataUri(conversationId));
- intent.putExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
- intent.putExtra(UIIntents.UI_INTENT_EXTRA_SELF_ID, selfId);
- intent.putExtra(UIIntents.UI_INTENT_EXTRA_REQUIRES_MMS, requiresMms);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- return getPendingIntentWithParentStack(context, intent, requestCode);
- }
-
- @Override
- public PendingIntent getPendingIntentForClearingNotifications(final Context context,
- final int updateTargets, final ConversationIdSet conversationIdSet,
- final int requestCode) {
- final Intent intent = new Intent(context, NotificationReceiver.class);
- intent.setAction(ACTION_RESET_NOTIFICATIONS);
- intent.putExtra(UI_INTENT_EXTRA_NOTIFICATIONS_UPDATE, updateTargets);
- if (conversationIdSet != null) {
- intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID_SET,
- conversationIdSet.getDelimitedString());
- }
- return PendingIntent.getBroadcast(context,
- requestCode, intent,
- PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- /**
- * Gets a PendingIntent associated with an Intent to start an Activity. All notifications
- * that starts an Activity must use this method to get a PendingIntent, which achieves two
- * goals:
- * 1. The target activities will be created, with any existing ones destroyed. This ensures
- * we don't end up with multiple instances of ConversationListActivity, for example.
- * 2. The target activity, when launched, will have its backstack correctly constructed so
- * back navigation will work correctly.
- */
- private static PendingIntent getPendingIntentWithParentStack(final Context context,
- final Intent intent, final int requestCode) {
- final TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
- // Adds the back stack for the Intent (plus the Intent itself)
- stackBuilder.addNextIntentWithParentStack(intent);
- final PendingIntent resultPendingIntent =
- stackBuilder.getPendingIntent(requestCode, PendingIntent.FLAG_UPDATE_CURRENT);
- return resultPendingIntent;
- }
-
- @Override
- public Intent getRingtonePickerIntent(final String title, final Uri existingUri,
- final Uri defaultUri, final int toneType) {
- return new Intent(RingtoneManager.ACTION_RINGTONE_PICKER)
- .putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, toneType)
- .putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, title)
- .putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, existingUri)
- .putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, defaultUri);
- }
-
- @Override
- public PendingIntent getPendingIntentForLowStorageNotifications(final Context context) {
- final TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(context);
- final Intent conversationListIntent = getConversationListActivityIntent(context);
- taskStackBuilder.addNextIntent(conversationListIntent);
- taskStackBuilder.addNextIntentWithParentStack(
- getSmsStorageLowWarningActivityIntent(context));
-
- return taskStackBuilder.getPendingIntent(
- 0, PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- @Override
- public PendingIntent getPendingIntentForSecondaryUserNewMessageNotification(
- final Context context) {
- return getPendingIntentForConversationListActivity(context);
- }
-
- @Override
- public Intent getWirelessAlertsIntent() {
- final Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setComponent(new ComponentName(CMAS_COMPONENT, CELL_BROADCAST_LIST_ACTIVITY));
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- return intent;
- }
-
- @Override
- public Intent getApnEditorIntent(final Context context, final String rowId, final int subId) {
- final Intent intent = new Intent(context, ApnEditorActivity.class);
- intent.putExtra(UI_INTENT_EXTRA_APN_ROW_ID, rowId);
- intent.putExtra(UI_INTENT_EXTRA_SUB_ID, subId);
- return intent;
- }
-
- @Override
- public Intent getApnSettingsIntent(final Context context, final int subId) {
- final Intent intent = new Intent(context, ApnSettingsActivity.class)
- .putExtra(UI_INTENT_EXTRA_SUB_ID, subId);
- return intent;
- }
-
- @Override
- public Intent getAdvancedSettingsIntent(final Context context) {
- return getPerSubscriptionSettingsIntent(context, ParticipantData.DEFAULT_SELF_SUB_ID, null);
- }
-
- @Override
- public Intent getChangeDefaultSmsAppIntent(final Activity activity) {
- final Intent intent = new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT);
- intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, activity.getPackageName());
- return intent;
- }
-
- @Override
- public void launchBrowserForUrl(final Context context, final String url) {
- final Intent intent = getViewUrlIntent(url);
- startExternalActivity(context, intent);
- }
-
- /**
- * Provides a safe way to handle external activities which may not exist.
- */
- private void startExternalActivity(final Context context, final Intent intent) {
- try {
- context.startActivity(intent);
- } catch (final ActivityNotFoundException ex) {
- LogUtil.w(LogUtil.BUGLE_TAG, "Couldn't find activity:", ex);
- UiUtils.showToastAtBottom(R.string.activity_not_found_message);
- }
- }
-
- private Intent getPerSubscriptionSettingsIntent(final Context context, final int subId,
- @Nullable final String settingTitle) {
- return new Intent(context, PerSubscriptionSettingsActivity.class)
- .putExtra(UI_INTENT_EXTRA_SUB_ID, subId)
- .putExtra(UI_INTENT_EXTRA_PER_SUBSCRIPTION_SETTING_TITLE, settingTitle);
- }
-
- @Override
- public Intent getLaunchConversationActivityIntent(final Context context) {
- final Intent intent = new Intent(context, LaunchConversationActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NO_HISTORY);
- return intent;
- }
-
- @Override
- public void kickMediaScanner(final Context context, final String volume) {
- final Intent intent = new Intent(MEDIA_SCANNER_SCAN_ACTION)
- .putExtra(MediaStore.MEDIA_SCANNER_VOLUME, volume)
- .setClassName(MEDIA_SCANNER_PACKAGE, MEDIA_SCANNER_CLASS);
- context.startService(intent);
- }
-
- @Override
- public PendingIntent getWidgetPendingIntentForConversationActivity(final Context context,
- final String conversationId, final int requestCode) {
- final Intent intent = getConversationActivityIntent(context, null, null,
- false /* withCustomTransition */);
- if (conversationId != null) {
- intent.putExtra(UI_INTENT_EXTRA_CONVERSATION_ID, conversationId);
-
- // Set the action to something unique to this conversation so if someone calls this
- // function again on a different conversation, they'll get a new PendingIntent instead
- // of the old one.
- intent.setAction(ACTION_WIDGET_CONVERSATION + conversationId);
- }
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- return getPendingIntentWithParentStack(context, intent, requestCode);
- }
-
- @Override
- public PendingIntent getWidgetPendingIntentForConversationListActivity(
- final Context context) {
- final Intent intent = getConversationListActivityIntent(context);
- return getPendingIntentWithParentStack(context, intent, 0);
- }
-
- @Override
- public PendingIntent getWidgetPendingIntentForConfigurationActivity(final Context context,
- final int appWidgetId) {
- final Intent configureIntent = new Intent(context, WidgetPickConversationActivity.class);
- configureIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
- configureIntent.setAction(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
- configureIntent.setData(Uri.parse(configureIntent.toUri(Intent.URI_INTENT_SCHEME)));
- configureIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY);
- return getPendingIntentWithParentStack(context, configureIntent, 0);
- }
-}
diff --git a/src/com/android/messaging/ui/VCardDetailActivity.java b/src/com/android/messaging/ui/VCardDetailActivity.java
deleted file mode 100644
index fecdc34..0000000
--- a/src/com/android/messaging/ui/VCardDetailActivity.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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;
-
-import android.app.Fragment;
-import android.net.Uri;
-import android.os.Bundle;
-import android.view.MenuItem;
-
-import com.android.messaging.R;
-import com.android.messaging.util.Assert;
-
-/**
- * An activity that hosts VCardDetailFragment that shows the content of a VCard that contains one
- * or more contacts.
- */
-public class VCardDetailActivity extends BugleActionBarActivity {
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.vcard_detail_activity);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- }
-
- @Override
- public void onAttachFragment(final Fragment fragment) {
- Assert.isTrue(fragment instanceof VCardDetailFragment);
- final Uri vCardUri = getIntent().getParcelableExtra(UIIntents.UI_INTENT_EXTRA_VCARD_URI);
- Assert.notNull(vCardUri);
- final VCardDetailFragment vCardDetailFragment = (VCardDetailFragment) fragment;
- vCardDetailFragment.setVCardUri(vCardUri);
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- // Treat the home press as back press so that when we go back to
- // ConversationActivity, it doesn't lose its original intent (conversation id etc.)
- onBackPressed();
- return true;
-
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/VCardDetailAdapter.java b/src/com/android/messaging/ui/VCardDetailAdapter.java
deleted file mode 100644
index cfdd836..0000000
--- a/src/com/android/messaging/ui/VCardDetailAdapter.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseExpandableListAdapter;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.media.VCardResourceEntry;
-import com.android.messaging.datamodel.media.VCardResourceEntry.VCardResourceEntryDestinationItem;
-
-import java.util.List;
-
-/**
- * Displays a list of expandable contact cards shown in the VCardDetailActivity.
- */
-public class VCardDetailAdapter extends BaseExpandableListAdapter {
- private final List<VCardResourceEntry> mVCards;
- private final LayoutInflater mInflater;
-
- public VCardDetailAdapter(final Context context, final List<VCardResourceEntry> vCards) {
- mVCards = vCards;
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
-
- @Override
- public Object getChild(final int groupPosition, final int childPosition) {
- return mVCards.get(groupPosition).getContactInfo().get(childPosition);
- }
-
- @Override
- public long getChildId(final int groupPosition, final int childPosition) {
- return childPosition;
- }
-
- @Override
- public View getChildView(final int groupPosition, final int childPosition,
- final boolean isLastChild, final View convertView, final ViewGroup parent) {
- PersonItemView v;
- if (convertView == null) {
- v = instantiateView(parent);
- } else {
- v = (PersonItemView) convertView;
- }
-
- final VCardResourceEntryDestinationItem item = (VCardResourceEntryDestinationItem)
- getChild(groupPosition, childPosition);
-
- v.bind(item.getDisplayItem());
- return v;
- }
-
- @Override
- public int getChildrenCount(final int groupPosition) {
- return mVCards.get(groupPosition).getContactInfo().size();
- }
-
- @Override
- public Object getGroup(final int groupPosition) {
- return mVCards.get(groupPosition);
- }
-
- @Override
- public int getGroupCount() {
- return mVCards.size();
- }
-
- @Override
- public long getGroupId(final int groupPosition) {
- return groupPosition;
- }
-
- @Override
- public View getGroupView(final int groupPosition, final boolean isExpanded,
- final View convertView, final ViewGroup parent) {
- PersonItemView v;
- if (convertView == null) {
- v = instantiateView(parent);
- } else {
- v = (PersonItemView) convertView;
- }
-
- final VCardResourceEntry item = (VCardResourceEntry) getGroup(groupPosition);
- v.bind(item.getDisplayItem());
- return v;
- }
-
- @Override
- public boolean isChildSelectable(final int groupPosition, final int childPosition) {
- return true;
- }
-
- @Override
- public boolean hasStableIds() {
- return true;
- }
-
- private PersonItemView instantiateView(final ViewGroup parent) {
- final PersonItemView v = (PersonItemView) mInflater.inflate(R.layout.people_list_item_view,
- parent, false);
- v.setClickable(false);
- return v;
- }
-}
diff --git a/src/com/android/messaging/ui/VCardDetailFragment.java b/src/com/android/messaging/ui/VCardDetailFragment.java
deleted file mode 100644
index 1b2b88d..0000000
--- a/src/com/android/messaging/ui/VCardDetailFragment.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * 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;
-
-import android.app.Fragment;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnLayoutChangeListener;
-import android.view.ViewGroup;
-import android.widget.ExpandableListAdapter;
-import android.widget.ExpandableListView;
-import android.widget.ExpandableListView.OnChildClickListener;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.PersonItemData;
-import com.android.messaging.datamodel.data.VCardContactItemData;
-import com.android.messaging.datamodel.data.PersonItemData.PersonItemDataListener;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.SafeAsyncTask;
-import com.android.messaging.util.UiUtils;
-import com.android.messaging.util.UriUtil;
-
-/**
- * A fragment that shows the content of a VCard that contains one or more contacts.
- */
-public class VCardDetailFragment extends Fragment implements PersonItemDataListener {
- private final Binding<VCardContactItemData> mBinding =
- BindingBase.createBinding(this);
- private ExpandableListView mListView;
- private VCardDetailAdapter mAdapter;
- private Uri mVCardUri;
-
- /**
- * We need to persist the VCard in the scratch directory before letting the user view it.
- * We save this Uri locally, so that if the user cancels the action and re-perform the add
- * to contacts action we don't have to persist it again.
- */
- private Uri mScratchSpaceUri;
-
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setHasOptionsMenu(true);
- }
-
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedInstanceState) {
- Assert.notNull(mVCardUri);
- final View view = inflater.inflate(R.layout.vcard_detail_fragment, container, false);
- mListView = (ExpandableListView) view.findViewById(R.id.list);
- mListView.addOnLayoutChangeListener(new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(final View v, final int left, final int top, final int right,
- final int bottom, final int oldLeft, final int oldTop, final int oldRight,
- final int oldBottom) {
- mListView.setIndicatorBounds(mListView.getWidth() - getResources()
- .getDimensionPixelSize(R.dimen.vcard_detail_group_indicator_width),
- mListView.getWidth());
- }
- });
- mListView.setOnChildClickListener(new OnChildClickListener() {
- @Override
- public boolean onChildClick(ExpandableListView expandableListView, View clickedView,
- int groupPosition, int childPosition, long childId) {
- if (!(clickedView instanceof PersonItemView)) {
- return false;
- }
- final Intent intent = ((PersonItemView) clickedView).getClickIntent();
- if (intent != null) {
- try {
- startActivity(intent);
- } catch (ActivityNotFoundException e) {
- return false;
- }
- return true;
- }
- return false;
- }
- });
- mBinding.bind(DataModel.get().createVCardContactItemData(getActivity(), mVCardUri));
- mBinding.getData().setListener(this);
- return view;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (mBinding.isBound()) {
- mBinding.unbind();
- }
- mListView.setAdapter((ExpandableListAdapter) null);
- }
-
- private boolean shouldShowAddToContactsItem() {
- return mBinding.isBound() && mBinding.getData().hasValidVCard();
- }
-
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- inflater.inflate(R.menu.vcard_detail_fragment_menu, menu);
- final MenuItem addToContactsItem = menu.findItem(R.id.action_add_contact);
- addToContactsItem.setVisible(shouldShowAddToContactsItem());
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case R.id.action_add_contact:
- mBinding.ensureBound();
- final Uri vCardUri = mBinding.getData().getVCardUri();
-
- // We have to do things in the background in case we need to copy the vcard data.
- new SafeAsyncTask<Void, Void, Uri>() {
- @Override
- protected Uri doInBackgroundTimed(final Void... params) {
- // We can't delete the persisted vCard file because we don't know when to
- // delete it, since the app that uses it (contacts, dialer) may start or
- // shut down at any point. Therefore, we rely on the system to clean up
- // the cache directory for us.
- return mScratchSpaceUri != null ? mScratchSpaceUri :
- UriUtil.persistContentToScratchSpace(vCardUri);
- }
-
- @Override
- protected void onPostExecute(final Uri result) {
- if (result != null) {
- mScratchSpaceUri = result;
- if (getActivity() != null) {
- MediaScratchFileProvider.addUriToDisplayNameEntry(
- result, mBinding.getData().getDisplayName());
- UIIntents.get().launchSaveVCardToContactsActivity(getActivity(),
- result);
- }
- }
- }
- }.executeOnThreadPool();
- return true;
-
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- public void setVCardUri(final Uri vCardUri) {
- Assert.isTrue(!mBinding.isBound());
- mVCardUri = vCardUri;
- }
-
- @Override
- public void onPersonDataUpdated(final PersonItemData data) {
- Assert.isTrue(data instanceof VCardContactItemData);
- mBinding.ensureBound();
- final VCardContactItemData vCardData = (VCardContactItemData) data;
- Assert.isTrue(vCardData.hasValidVCard());
- mAdapter = new VCardDetailAdapter(getActivity(), vCardData.getVCardResource().getVCards());
- mListView.setAdapter(mAdapter);
-
- // Expand the contact card if there's only one contact.
- if (mAdapter.getGroupCount() == 1) {
- mListView.expandGroup(0);
- }
- getActivity().invalidateOptionsMenu();
- }
-
- @Override
- public void onPersonDataFailed(final PersonItemData data, final Exception exception) {
- mBinding.ensureBound();
- UiUtils.showToastAtBottom(R.string.failed_loading_vcard);
- getActivity().finish();
- }
-}
diff --git a/src/com/android/messaging/ui/VideoThumbnailView.java b/src/com/android/messaging/ui/VideoThumbnailView.java
deleted file mode 100644
index 966336e..0000000
--- a/src/com/android/messaging/ui/VideoThumbnailView.java
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.media.MediaPlayer;
-import android.net.Uri;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.ImageButton;
-import android.widget.ImageView.ScaleType;
-import android.widget.VideoView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.media.ImageRequest;
-import com.android.messaging.datamodel.media.MessagePartVideoThumbnailRequestDescriptor;
-import com.android.messaging.datamodel.media.VideoThumbnailRequest;
-import com.android.messaging.util.Assert;
-
-/**
- * View that encapsulates a video preview (either as a thumbnail image, or video player), and the
- * a play button to overlay it. Ensures that the video preview maintains the aspect ratio of the
- * original video while trying to respect minimum width/height and constraining to the available
- * bounds
- */
-public class VideoThumbnailView extends FrameLayout {
- /**
- * When in this mode the VideoThumbnailView is a lightweight AsyncImageView with an ImageButton
- * to play the video. Clicking play will launch a full screen player
- */
- private static final int MODE_IMAGE_THUMBNAIL = 0;
-
- /**
- * When in this mode the VideoThumbnailVideo will include a VideoView, and the play button will
- * play the video inline. When in this mode, the loop and playOnLoad attributes can be applied
- * to auto-play or loop the video.
- */
- private static final int MODE_PLAYABLE_VIDEO = 1;
-
- private final int mMode;
- private final boolean mPlayOnLoad;
- private final boolean mAllowCrop;
- private final VideoView mVideoView;
- private final ImageButton mPlayButton;
- private final AsyncImageView mThumbnailImage;
- private int mVideoWidth;
- private int mVideoHeight;
- private Uri mVideoSource;
- private boolean mAnimating;
- private boolean mVideoLoaded;
-
- public VideoThumbnailView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- final TypedArray typedAttributes =
- context.obtainStyledAttributes(attrs, R.styleable.VideoThumbnailView);
-
- final LayoutInflater inflater = LayoutInflater.from(context);
- inflater.inflate(R.layout.video_thumbnail_view, this, true);
-
- mPlayOnLoad = typedAttributes.getBoolean(R.styleable.VideoThumbnailView_playOnLoad, false);
- final boolean loop =
- typedAttributes.getBoolean(R.styleable.VideoThumbnailView_loop, false);
- mMode = typedAttributes.getInt(R.styleable.VideoThumbnailView_mode, MODE_IMAGE_THUMBNAIL);
- mAllowCrop = typedAttributes.getBoolean(R.styleable.VideoThumbnailView_allowCrop, false);
-
- mVideoWidth = ImageRequest.UNSPECIFIED_SIZE;
- mVideoHeight = ImageRequest.UNSPECIFIED_SIZE;
-
- if (mMode == MODE_PLAYABLE_VIDEO) {
- mVideoView = new VideoView(context);
- // Video view tries to request focus on start which pulls focus from the user's intended
- // focus when we add this control. Remove focusability to prevent this. The play
- // button can still be focused
- mVideoView.setFocusable(false);
- mVideoView.setFocusableInTouchMode(false);
- mVideoView.clearFocus();
- addView(mVideoView, 0, new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
- mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
- @Override
- public void onPrepared(final MediaPlayer mediaPlayer) {
- mVideoLoaded = true;
- mVideoWidth = mediaPlayer.getVideoWidth();
- mVideoHeight = mediaPlayer.getVideoHeight();
- mediaPlayer.setLooping(loop);
- trySwitchToVideo();
- }
- });
- mVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
- @Override
- public void onCompletion(final MediaPlayer mediaPlayer) {
- mPlayButton.setVisibility(View.VISIBLE);
- }
- });
- mVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() {
- @Override
- public boolean onError(final MediaPlayer mediaPlayer, final int i, final int i2) {
- return true;
- }
- });
- } else {
- mVideoView = null;
- }
-
- mPlayButton = (ImageButton) findViewById(R.id.video_thumbnail_play_button);
- if (loop) {
- mPlayButton.setVisibility(View.GONE);
- } else {
- mPlayButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View view) {
- if (mVideoSource == null) {
- return;
- }
-
- if (mMode == MODE_PLAYABLE_VIDEO) {
- mVideoView.seekTo(0);
- start();
- } else {
- UIIntents.get().launchFullScreenVideoViewer(getContext(), mVideoSource);
- }
- }
- });
- mPlayButton.setOnLongClickListener(new OnLongClickListener() {
- @Override
- public boolean onLongClick(final View view) {
- // Button prevents long click from propagating up, do it manually
- VideoThumbnailView.this.performLongClick();
- return true;
- }
- });
- }
-
- mThumbnailImage = (AsyncImageView) findViewById(R.id.video_thumbnail_image);
- if (mAllowCrop) {
- mThumbnailImage.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
- mThumbnailImage.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
- mThumbnailImage.setScaleType(ScaleType.CENTER_CROP);
- } else {
- // This is the default setting in the layout, so No-op.
- }
- final int maxHeight = typedAttributes.getDimensionPixelSize(
- R.styleable.VideoThumbnailView_android_maxHeight, ImageRequest.UNSPECIFIED_SIZE);
- if (maxHeight != ImageRequest.UNSPECIFIED_SIZE) {
- mThumbnailImage.setMaxHeight(maxHeight);
- mThumbnailImage.setAdjustViewBounds(true);
- }
-
- typedAttributes.recycle();
- }
-
- @Override
- protected void onAnimationStart() {
- super.onAnimationStart();
- mAnimating = true;
- }
-
- @Override
- protected void onAnimationEnd() {
- super.onAnimationEnd();
- mAnimating = false;
- trySwitchToVideo();
- }
-
- private void trySwitchToVideo() {
- if (mAnimating) {
- // Don't start video or hide image until after animation completes
- return;
- }
-
- if (!mVideoLoaded) {
- // Video hasn't loaded, nothing more to do
- return;
- }
-
- if (mPlayOnLoad) {
- start();
- } else {
- mVideoView.seekTo(0);
- }
- }
-
- private boolean hasVideoSize() {
- return mVideoWidth != ImageRequest.UNSPECIFIED_SIZE &&
- mVideoHeight != ImageRequest.UNSPECIFIED_SIZE;
- }
-
- public void start() {
- Assert.equals(MODE_PLAYABLE_VIDEO, mMode);
- mPlayButton.setVisibility(View.GONE);
- mThumbnailImage.setVisibility(View.GONE);
- mVideoView.start();
- }
-
- // TODO: The check could be added to MessagePartData itself so that all users of MessagePartData
- // get the right behavior, instead of requiring all the users to do similar checks.
- private static boolean shouldUseGenericVideoIcon(final boolean incomingMessage) {
- return incomingMessage && !VideoThumbnailRequest.shouldShowIncomingVideoThumbnails();
- }
-
- public void setSource(final MessagePartData part, final boolean incomingMessage) {
- if (part == null) {
- clearSource();
- } else {
- mVideoSource = part.getContentUri();
- if (shouldUseGenericVideoIcon(incomingMessage)) {
- mThumbnailImage.setImageResource(R.drawable.generic_video_icon);
- mVideoWidth = ImageRequest.UNSPECIFIED_SIZE;
- mVideoHeight = ImageRequest.UNSPECIFIED_SIZE;
- } else {
- mThumbnailImage.setImageResourceId(
- new MessagePartVideoThumbnailRequestDescriptor(part));
- if (mVideoView != null) {
- mVideoView.setVideoURI(mVideoSource);
- }
- mVideoWidth = part.getWidth();
- mVideoHeight = part.getHeight();
- }
- }
- }
-
- public void setSource(final Uri videoSource, final boolean incomingMessage) {
- if (videoSource == null) {
- clearSource();
- } else {
- mVideoSource = videoSource;
- if (shouldUseGenericVideoIcon(incomingMessage)) {
- mThumbnailImage.setImageResource(R.drawable.generic_video_icon);
- mVideoWidth = ImageRequest.UNSPECIFIED_SIZE;
- mVideoHeight = ImageRequest.UNSPECIFIED_SIZE;
- } else {
- mThumbnailImage.setImageResourceId(
- new MessagePartVideoThumbnailRequestDescriptor(videoSource));
- if (mVideoView != null) {
- mVideoView.setVideoURI(videoSource);
- }
- }
- }
- }
-
- private void clearSource() {
- mVideoSource = null;
- mThumbnailImage.setImageResourceId(null);
- mVideoWidth = ImageRequest.UNSPECIFIED_SIZE;
- mVideoHeight = ImageRequest.UNSPECIFIED_SIZE;
- if (mVideoView != null) {
- mVideoView.setVideoURI(null);
- }
- }
-
- @Override
- public void setMinimumWidth(final int minWidth) {
- super.setMinimumWidth(minWidth);
- if (mVideoView != null) {
- mVideoView.setMinimumWidth(minWidth);
- }
- }
-
- @Override
- public void setMinimumHeight(final int minHeight) {
- super.setMinimumHeight(minHeight);
- if (mVideoView != null) {
- mVideoView.setMinimumHeight(minHeight);
- }
- }
-
- public void setColorFilter(int color) {
- mThumbnailImage.setColorFilter(color);
- mPlayButton.setColorFilter(color);
- }
-
- public void clearColorFilter() {
- mThumbnailImage.clearColorFilter();
- mPlayButton.clearColorFilter();
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- if (mAllowCrop) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- return;
- }
- int desiredWidth = 1;
- int desiredHeight = 1;
- if (mVideoView != null) {
- mVideoView.measure(widthMeasureSpec, heightMeasureSpec);
- }
- mThumbnailImage.measure(widthMeasureSpec, heightMeasureSpec);
- if (hasVideoSize()) {
- desiredWidth = mVideoWidth;
- desiredHeight = mVideoHeight;
- } else {
- desiredWidth = mThumbnailImage.getMeasuredWidth();
- desiredHeight = mThumbnailImage.getMeasuredHeight();
- }
-
- final int minimumWidth = getMinimumWidth();
- final int minimumHeight = getMinimumHeight();
-
- // Constrain the scale to fit within the supplied size
- final float maxScale = Math.max(
- MeasureSpec.getSize(widthMeasureSpec) / (float) desiredWidth,
- MeasureSpec.getSize(heightMeasureSpec) / (float) desiredHeight);
-
- // Scale up to reach minimum width/height
- final float widthScale = Math.max(1, minimumWidth / (float) desiredWidth);
- final float heightScale = Math.max(1, minimumHeight / (float) desiredHeight);
- final float scale = Math.min(maxScale, Math.max(widthScale, heightScale));
- desiredWidth = (int) (desiredWidth * scale);
- desiredHeight = (int) (desiredHeight * scale);
-
- setMeasuredDimension(desiredWidth, desiredHeight);
- }
-
- @Override
- protected void onLayout(final boolean changed, final int left, final int top, final int right,
- final int bottom) {
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- child.layout(0, 0, right - left, bottom - top);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/ViewPagerTabStrip.java b/src/com/android/messaging/ui/ViewPagerTabStrip.java
deleted file mode 100644
index e088296..0000000
--- a/src/com/android/messaging/ui/ViewPagerTabStrip.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import com.android.messaging.R;
-import com.android.messaging.util.OsUtil;
-
-public class ViewPagerTabStrip extends LinearLayout {
- private int mSelectedUnderlineThickness;
- private final Paint mSelectedUnderlinePaint;
-
- private int mIndexForSelection;
- private float mSelectionOffset;
-
- public ViewPagerTabStrip(Context context) {
- this(context, null);
- }
-
- public ViewPagerTabStrip(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- final Resources res = context.getResources();
-
- mSelectedUnderlineThickness =
- res.getDimensionPixelSize(R.dimen.pager_tab_underline_selected);
- int underlineColor = res.getColor(R.color.contact_picker_tab_underline);
- int backgroundColor = res.getColor(R.color.action_bar_background_color);
-
- mSelectedUnderlinePaint = new Paint();
- mSelectedUnderlinePaint.setColor(underlineColor);
-
- setBackgroundColor(backgroundColor);
- setWillNotDraw(false);
- }
-
- /**
- * Notifies this view that view pager has been scrolled. We save the tab index
- * and selection offset for interpolating the position and width of selection
- * underline.
- */
- void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- mIndexForSelection = position;
- mSelectionOffset = positionOffset;
- invalidate();
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- int childCount = getChildCount();
-
- // Thick colored underline below the current selection
- if (childCount > 0) {
- View selectedTitle = getChildAt(mIndexForSelection);
- int selectedLeft = selectedTitle.getLeft();
- int selectedRight = selectedTitle.getRight();
- final boolean isRtl = isRtl();
- final boolean hasNextTab = isRtl ? mIndexForSelection > 0
- : (mIndexForSelection < (getChildCount() - 1));
- if ((mSelectionOffset > 0.0f) && hasNextTab) {
- // Draw the selection partway between the tabs
- View nextTitle = getChildAt(mIndexForSelection + (isRtl ? -1 : 1));
- int nextLeft = nextTitle.getLeft();
- int nextRight = nextTitle.getRight();
-
- selectedLeft = (int) (mSelectionOffset * nextLeft +
- (1.0f - mSelectionOffset) * selectedLeft);
- selectedRight = (int) (mSelectionOffset * nextRight +
- (1.0f - mSelectionOffset) * selectedRight);
- }
-
- int height = getHeight();
- canvas.drawRect(selectedLeft, height - mSelectedUnderlineThickness,
- selectedRight, height, mSelectedUnderlinePaint);
- }
- }
-
- private boolean isRtl() {
- return OsUtil.isAtLeastJB_MR2() ? getLayoutDirection() == View.LAYOUT_DIRECTION_RTL : false;
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/ViewPagerTabs.java b/src/com/android/messaging/ui/ViewPagerTabs.java
deleted file mode 100644
index f31a071..0000000
--- a/src/com/android/messaging/ui/ViewPagerTabs.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.Outline;
-import android.graphics.drawable.ColorDrawable;
-import android.support.v4.view.PagerAdapter;
-import android.support.v4.view.ViewPager;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.util.OsUtil;
-
-/**
- * Lightweight implementation of ViewPager tabs. This looks similar to traditional actionBar tabs,
- * but allows for the view containing the tabs to be placed anywhere on screen. Text-related
- * attributes can also be assigned in XML - these will get propogated to the child TextViews
- * automatically.
- *
- * Note: this file is taken from the AOSP /packages/apps/ContactsCommon/src/com/android/contacts/
- * common/list/ViewPagerTabs.java. Some platform specific API calls (e.g. ViewOutlineProvider which
- * assumes L and above) have been modified to support down to Api Level 16.
- */
-public class ViewPagerTabs extends HorizontalScrollView implements ViewPager.OnPageChangeListener {
-
- ViewPager mPager;
- private ViewPagerTabStrip mTabStrip;
-
- /**
- * Linearlayout that will contain the TextViews serving as tabs. This is the only child
- * of the parent HorizontalScrollView.
- */
- final int mTextStyle;
- final ColorStateList mTextColor;
- final int mTextSize;
- final boolean mTextAllCaps;
- int mPrevSelected = -1;
- int mSidePadding;
-
- private static final int TAB_SIDE_PADDING_IN_DPS = 10;
-
- // TODO: This should use <declare-styleable> in the future
- private static final int[] ATTRS = new int[] {
- android.R.attr.textSize,
- android.R.attr.textStyle,
- android.R.attr.textColor,
- android.R.attr.textAllCaps
- };
-
- /**
- * Shows a toast with the tab description when long-clicked.
- */
- private class OnTabLongClickListener implements OnLongClickListener {
- final String mTabDescription;
-
- public OnTabLongClickListener(String tabDescription) {
- mTabDescription = tabDescription;
- }
-
- @Override
- public boolean onLongClick(View v) {
- final int[] screenPos = new int[2];
- getLocationOnScreen(screenPos);
-
- final Context context = getContext();
- final int width = getWidth();
- final int height = getHeight();
- final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
-
- Toast toast = Toast.makeText(context, mTabDescription, Toast.LENGTH_SHORT);
-
- // Show the toast under the tab
- toast.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
- (screenPos[0] + width / 2) - screenWidth / 2, screenPos[1] + height);
-
- toast.show();
- return true;
- }
- }
-
- public ViewPagerTabs(Context context) {
- this(context, null);
- }
-
- public ViewPagerTabs(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public ViewPagerTabs(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- setFillViewport(true);
-
- mSidePadding = (int) (getResources().getDisplayMetrics().density * TAB_SIDE_PADDING_IN_DPS);
-
- final TypedArray a = context.obtainStyledAttributes(attrs, ATTRS);
- mTextSize = a.getDimensionPixelSize(0, 0);
- mTextStyle = a.getInt(1, 0);
- mTextColor = a.getColorStateList(2);
- mTextAllCaps = a.getBoolean(3, false);
-
- mTabStrip = new ViewPagerTabStrip(context);
- addView(mTabStrip,
- new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
- a.recycle();
-
- // enable shadow casting from view bounds
- if (OsUtil.isAtLeastL()) {
- setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRect(0, 0, view.getWidth(), view.getHeight());
- }
- });
- }
- }
-
- public void setViewPager(ViewPager viewPager) {
- mPager = viewPager;
- addTabs(mPager.getAdapter());
- }
-
- private void addTabs(PagerAdapter adapter) {
- mTabStrip.removeAllViews();
-
- final int count = adapter.getCount();
- for (int i = 0; i < count; i++) {
- addTab(adapter.getPageTitle(i), i);
- }
- }
-
- private void addTab(CharSequence tabTitle, final int position) {
- final TextView textView = new TextView(getContext());
- textView.setText(tabTitle);
- textView.setBackgroundResource(R.drawable.contact_picker_tab_background_selector);
- textView.setGravity(Gravity.CENTER);
- textView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mPager.setCurrentItem(getRtlPosition(position));
- }
- });
-
- // Assign various text appearance related attributes to child views.
- if (mTextStyle > 0) {
- textView.setTypeface(textView.getTypeface(), mTextStyle);
- }
- if (mTextSize > 0) {
- textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
- }
- if (mTextColor != null) {
- textView.setTextColor(mTextColor);
- }
- textView.setAllCaps(mTextAllCaps);
- textView.setPadding(mSidePadding, 0, mSidePadding, 0);
- mTabStrip.addView(textView, new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.MATCH_PARENT, 1));
- // Default to the first child being selected
- if (position == 0) {
- mPrevSelected = 0;
- textView.setSelected(true);
- }
- }
-
- @Override
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- position = getRtlPosition(position);
- int tabStripChildCount = mTabStrip.getChildCount();
- if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
- return;
- }
-
- mTabStrip.onPageScrolled(position, positionOffset, positionOffsetPixels);
- }
-
- @Override
- public void onPageSelected(int position) {
- position = getRtlPosition(position);
- int tabStripChildCount = mTabStrip.getChildCount();
- if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) {
- return;
- }
-
- if (mPrevSelected >= 0 && mPrevSelected < tabStripChildCount) {
- mTabStrip.getChildAt(mPrevSelected).setSelected(false);
- }
- final View selectedChild = mTabStrip.getChildAt(position);
- selectedChild.setSelected(true);
-
- // Update scroll position
- final int scrollPos = selectedChild.getLeft() - (getWidth() - selectedChild.getWidth()) / 2;
- smoothScrollTo(scrollPos, 0);
- mPrevSelected = position;
- }
-
- @Override
- public void onPageScrollStateChanged(int state) {
- }
-
- private int getRtlPosition(int position) {
- if (OsUtil.isAtLeastJB_MR2() && Factory.get().getApplicationContext().getResources()
- .getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
- return mTabStrip.getChildCount() - 1 - position;
- }
- return position;
- }
-
- public int getSelectedItemPosition() {
- return mPrevSelected;
- }
-}
diff --git a/src/com/android/messaging/ui/WidgetPickConversationActivity.java b/src/com/android/messaging/ui/WidgetPickConversationActivity.java
deleted file mode 100644
index 60e1318..0000000
--- a/src/com/android/messaging/ui/WidgetPickConversationActivity.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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;
-
-import android.app.Fragment;
-import android.appwidget.AppWidgetManager;
-import android.content.Intent;
-import android.os.Bundle;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.ui.conversationlist.ShareIntentFragment;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.widget.WidgetConversationProvider;
-
-public class WidgetPickConversationActivity extends BaseBugleActivity implements
- ShareIntentFragment.HostInterface {
-
- private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
-
- // Set the result to CANCELED. This will cause the widget host to cancel
- // out of the widget placement if they press the back button.
- setResult(RESULT_CANCELED);
-
- // Find the widget id from the intent.
- final Intent intent = getIntent();
- final Bundle extras = intent.getExtras();
- if (extras != null) {
- mAppWidgetId = extras.getInt(
- AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
- }
-
- // If they gave us an intent without the widget id, just bail.
- if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
- finish();
- }
-
- final ShareIntentFragment convPicker = new ShareIntentFragment();
- final Bundle bundle = new Bundle();
- bundle.putBoolean(ShareIntentFragment.HIDE_NEW_CONVERSATION_BUTTON_KEY, true);
- convPicker.setArguments(bundle);
- convPicker.show(getFragmentManager(), "ShareIntentFragment");
- }
-
- @Override
- public void onAttachFragment(final Fragment fragment) {
- final Intent intent = getIntent();
- final String action = intent.getAction();
- if (!AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)) {
- // Unsupported action.
- Assert.fail("Unsupported action type: " + action);
- }
- }
-
- @Override
- public void onConversationClick(final ConversationListItemData conversationListItemData) {
- saveConversationidPref(mAppWidgetId, conversationListItemData.getConversationId());
-
- // Push widget update to surface with newly set prefix
- WidgetConversationProvider.rebuildWidget(this, mAppWidgetId);
-
- // Make sure we pass back the original appWidgetId
- Intent resultValue = new Intent();
- resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
- setResult(RESULT_OK, resultValue);
- finish();
- }
-
- @Override
- public void onCreateConversationClick() {
- // We should never get here because we're hiding the new conversation button in the
- // ShareIntentFragment by setting HIDE_NEW_CONVERSATION_BUTTON_KEY in the arguments.
- finish();
- }
-
- // Write the ConversationId to the SharedPreferences object for this widget
- static void saveConversationidPref(int appWidgetId, String conversationId) {
- final BuglePrefs prefs = Factory.get().getWidgetPrefs();
- prefs.putString(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID + appWidgetId, conversationId);
- }
-
- // Read the ConversationId from the SharedPreferences object for this widget.
- public static String getConversationIdPref(int appWidgetId) {
- final BuglePrefs prefs = Factory.get().getWidgetPrefs();
- return prefs.getString(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID + appWidgetId, null);
- }
-
- // Delete the ConversationId preference from the SharedPreferences object for this widget.
- public static void deleteConversationIdPref(int appWidgetId) {
- final BuglePrefs prefs = Factory.get().getWidgetPrefs();
- prefs.remove(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID + appWidgetId);
- }
-
-}
diff --git a/src/com/android/messaging/ui/animation/PopupTransitionAnimation.java b/src/com/android/messaging/ui/animation/PopupTransitionAnimation.java
deleted file mode 100644
index 21529c6..0000000
--- a/src/com/android/messaging/ui/animation/PopupTransitionAnimation.java
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * 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.animation;
-
-import android.animation.TypeEvaluator;
-import android.app.Activity;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.Animation;
-import android.view.animation.Transformation;
-import android.widget.PopupWindow;
-
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.ThreadUtil;
-import com.android.messaging.util.UiUtils;
-
-/**
- * Animates viewToAnimate from startRect to the place where it is in the layout, viewToAnimate
- * should be in its final destination location before startAfterLayoutComplete is called.
- * viewToAnimate will be drawn scaled and offset in a popupWindow.
- * This class handles the case where the viewToAnimate moves during the animation
- */
-public class PopupTransitionAnimation extends Animation {
- /** The view we're animating */
- private final View mViewToAnimate;
-
- /** The rect to start the slide in animation from */
- private final Rect mStartRect;
-
- /** The rect of the currently animated view */
- private Rect mCurrentRect;
-
- /** The rect that we're animating to. This can change during the animation */
- private final Rect mDestRect;
-
- /** The bounds of the popup in window coordinates. Does not include notification bar */
- private final Rect mPopupRect;
-
- /** The bounds of the action bar in window coordinates. We clip the popup to below this */
- private final Rect mActionBarRect;
-
- /** Interpolates between the start and end rect for every animation tick */
- private final TypeEvaluator<Rect> mRectEvaluator;
-
- /** The popup window that holds contains the animating view */
- private PopupWindow mPopupWindow;
-
- /** The layout root for the popup which is where the animated view is rendered */
- private View mPopupRoot;
-
- /** The action bar's view */
- private final View mActionBarView;
-
- private Runnable mOnStartCallback;
- private Runnable mOnStopCallback;
-
- public PopupTransitionAnimation(final Rect startRect, final View viewToAnimate) {
- mViewToAnimate = viewToAnimate;
- mStartRect = startRect;
- mCurrentRect = new Rect(mStartRect);
- mDestRect = new Rect();
- mPopupRect = new Rect();
- mActionBarRect = new Rect();
- final Activity activity = (Activity) viewToAnimate.getRootView().getContext();
- mActionBarView = activity.getWindow().getDecorView().findViewById(
- android.support.v7.appcompat.R.id.action_bar);
- mRectEvaluator = RectEvaluatorCompat.create();
- setDuration(UiUtils.MEDIAPICKER_TRANSITION_DURATION);
- setInterpolator(UiUtils.DEFAULT_INTERPOLATOR);
- setAnimationListener(new AnimationListener() {
- @Override
- public void onAnimationStart(final Animation animation) {
- if (mOnStartCallback != null) {
- mOnStartCallback.run();
- }
- mEvents.append("oAS,");
- }
-
- @Override
- public void onAnimationEnd(final Animation animation) {
- if (mOnStopCallback != null) {
- mOnStopCallback.run();
- }
- dismiss();
- mEvents.append("oAE,");
- }
-
- @Override
- public void onAnimationRepeat(final Animation animation) {
- }
- });
- }
-
- private final StringBuilder mEvents = new StringBuilder();
- private final Runnable mCleanupRunnable = new Runnable() {
- @Override
- public void run() {
- LogUtil.w(LogUtil.BUGLE_TAG, "PopupTransitionAnimation: " + mEvents);
- }
- };
-
- /**
- * Ensures the animation is ready before starting the animation.
- * viewToAnimate must first be layed out so we know where we will animate to
- */
- public void startAfterLayoutComplete() {
- // We want layout to occur, and then we immediately animate it in, so hide it initially to
- // reduce jank on the first frame
- mViewToAnimate.setVisibility(View.INVISIBLE);
- mViewToAnimate.setAlpha(0);
-
- final Runnable startAnimation = new Runnable() {
- boolean mRunComplete = false;
- boolean mFirstTry = true;
-
- @Override
- public void run() {
- if (mRunComplete) {
- return;
- }
-
- mViewToAnimate.getGlobalVisibleRect(mDestRect);
- // In Android views which are visible but haven't computed their size yet have a
- // size of 1x1 because anything with a size of 0x0 is considered hidden. We can't
- // start the animation until after the size is greater than 1x1
- if (mDestRect.width() <= 1 || mDestRect.height() <= 1) {
- // Layout hasn't occurred yet
- if (!mFirstTry) {
- // Give up if this is not the first try, since layout change still doesn't
- // yield a size for the view. This is likely because the media picker is
- // full screen so there's no space left for the animated view. We give up
- // on animation, but need to make sure the view that was initially
- // hidden is re-shown.
- mViewToAnimate.setAlpha(1);
- mViewToAnimate.setVisibility(View.VISIBLE);
- } else {
- mFirstTry = false;
- UiUtils.doOnceAfterLayoutChange(mViewToAnimate, this);
- }
- return;
- }
-
- mRunComplete = true;
- mViewToAnimate.startAnimation(PopupTransitionAnimation.this);
- mViewToAnimate.invalidate();
- // http://b/20856505: The PopupWindow sometimes does not get dismissed.
- ThreadUtil.getMainThreadHandler().postDelayed(mCleanupRunnable, getDuration() * 2);
- }
- };
-
- startAnimation.run();
- }
-
- public PopupTransitionAnimation setOnStartCallback(final Runnable onStart) {
- mOnStartCallback = onStart;
- return this;
- }
-
- public PopupTransitionAnimation setOnStopCallback(final Runnable onStop) {
- mOnStopCallback = onStop;
- return this;
- }
-
- @Override
- protected void applyTransformation(final float interpolatedTime, final Transformation t) {
- if (mPopupWindow == null) {
- initPopupWindow();
- }
- // Update mDestRect as it may have moved during the animation
- mPopupRect.set(UiUtils.getMeasuredBoundsOnScreen(mPopupRoot));
- mActionBarRect.set(UiUtils.getMeasuredBoundsOnScreen(mActionBarView));
- computeDestRect();
-
- // Update currentRect to the new animated coordinates, and request mPopupRoot to redraw
- // itself at the new coordinates
- mCurrentRect = mRectEvaluator.evaluate(interpolatedTime, mStartRect, mDestRect);
- mPopupRoot.invalidate();
-
- if (interpolatedTime >= 0.98) {
- mEvents.append("aT").append(interpolatedTime).append(',');
- }
- if (interpolatedTime == 1) {
- dismiss();
- }
- }
-
- private void dismiss() {
- mEvents.append("d,");
- mViewToAnimate.setAlpha(1);
- mViewToAnimate.setVisibility(View.VISIBLE);
- // Delay dismissing the popup window to let mViewToAnimate draw under it and reduce the
- // flash
- ThreadUtil.getMainThreadHandler().post(new Runnable() {
- @Override
- public void run() {
- try {
- mPopupWindow.dismiss();
- } catch (IllegalArgumentException e) {
- // PopupWindow.dismiss() will fire an IllegalArgumentException if the activity
- // has already ended while we were animating
- }
- ThreadUtil.getMainThreadHandler().removeCallbacks(mCleanupRunnable);
- }
- });
- }
-
- @Override
- public boolean willChangeBounds() {
- return false;
- }
-
- /**
- * Computes mDestRect (the position in window space of the placeholder view that we should
- * animate to). Some frames during the animation fail to compute getGlobalVisibleRect, so use
- * the last known values in that case
- */
- private void computeDestRect() {
- final int prevTop = mDestRect.top;
- final int prevLeft = mDestRect.left;
- final int prevRight = mDestRect.right;
- final int prevBottom = mDestRect.bottom;
-
- if (!getViewScreenMeasureRect(mViewToAnimate, mDestRect)) {
- mDestRect.top = prevTop;
- mDestRect.left = prevLeft;
- mDestRect.bottom = prevBottom;
- mDestRect.right = prevRight;
- }
- }
-
- /**
- * Sets up the PopupWindow that the view will animate in. Animating the size and position of a
- * popup can be choppy, so instead we make the popup fill the entire space of the screen, and
- * animate the position of viewToAnimate within the popup using a Transformation
- */
- private void initPopupWindow() {
- mPopupRoot = new View(mViewToAnimate.getContext()) {
- @Override
- protected void onDraw(final Canvas canvas) {
- canvas.save();
- canvas.clipRect(getLeft(), mActionBarRect.bottom - mPopupRect.top, getRight(),
- getBottom());
- canvas.drawColor(Color.TRANSPARENT);
- final float previousAlpha = mViewToAnimate.getAlpha();
- mViewToAnimate.setAlpha(1);
- // The view's global position includes the notification bar height, but
- // the popup window may or may not cover the notification bar (depending on screen
- // rotation, IME status etc.), so we need to compensate for this difference by
- // offseting vertically.
- canvas.translate(mCurrentRect.left, mCurrentRect.top - mPopupRect.top);
-
- final float viewWidth = mViewToAnimate.getWidth();
- final float viewHeight = mViewToAnimate.getHeight();
- if (viewWidth > 0 && viewHeight > 0) {
- canvas.scale(mCurrentRect.width() / viewWidth,
- mCurrentRect.height() / viewHeight);
- }
- canvas.clipRect(0, 0, mCurrentRect.width(), mCurrentRect.height());
- if (!mPopupRect.isEmpty()) {
- // HACK: Layout is unstable until mPopupRect is non-empty.
- mViewToAnimate.draw(canvas);
- }
- mViewToAnimate.setAlpha(previousAlpha);
- canvas.restore();
- }
- };
- mPopupWindow = new PopupWindow(mViewToAnimate.getContext());
- mPopupWindow.setBackgroundDrawable(null);
- mPopupWindow.setContentView(mPopupRoot);
- mPopupWindow.setWidth(ViewGroup.LayoutParams.MATCH_PARENT);
- mPopupWindow.setHeight(ViewGroup.LayoutParams.MATCH_PARENT);
- mPopupWindow.setTouchable(false);
- // We must pass a non-zero value for the y offset, or else the system resets the status bar
- // color to black (M only) during the animation. The actual position of the window (and
- // the animated view inside it) are still correct, regardless of what we pass for the y
- // parameter (e.g. 1 and 100 both work). Not entirely sure why this works.
- mPopupWindow.showAtLocation(mViewToAnimate, Gravity.TOP, 0, 1);
- }
-
- private static boolean getViewScreenMeasureRect(final View view, final Rect outRect) {
- outRect.set(UiUtils.getMeasuredBoundsOnScreen(view));
- return !outRect.isEmpty();
- }
-}
diff --git a/src/com/android/messaging/ui/animation/RectEvaluatorCompat.java b/src/com/android/messaging/ui/animation/RectEvaluatorCompat.java
deleted file mode 100644
index e3c60fc..0000000
--- a/src/com/android/messaging/ui/animation/RectEvaluatorCompat.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.animation;
-
-import android.animation.RectEvaluator;
-import android.animation.TypeEvaluator;
-import android.graphics.Rect;
-
-import com.android.messaging.util.OsUtil;
-
-/**
- * This evaluator can be used to perform type interpolation between <code>Rect</code> values.
- * It's backward compatible to Api Level 11.
- */
-public class RectEvaluatorCompat implements TypeEvaluator<Rect> {
- public static TypeEvaluator<Rect> create() {
- if (OsUtil.isAtLeastJB_MR2()) {
- return new RectEvaluator();
- } else {
- return new RectEvaluatorCompat();
- }
- }
-
- @Override
- public Rect evaluate(float fraction, Rect startValue, Rect endValue) {
- int left = startValue.left + (int) ((endValue.left - startValue.left) * fraction);
- int top = startValue.top + (int) ((endValue.top - startValue.top) * fraction);
- int right = startValue.right + (int) ((endValue.right - startValue.right) * fraction);
- int bottom = startValue.bottom + (int) ((endValue.bottom - startValue.bottom) * fraction);
- return new Rect(left, top, right, bottom);
- }
-}
diff --git a/src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java b/src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java
deleted file mode 100644
index 6abfdf9..0000000
--- a/src/com/android/messaging/ui/animation/ViewGroupItemVerticalExplodeAnimation.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * 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.animation;
-
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.support.v4.view.ViewCompat;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroupOverlay;
-import android.view.ViewOverlay;
-import android.widget.FrameLayout;
-
-import com.android.messaging.R;
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.UiUtils;
-
-/**
- * <p>
- * Shows a vertical "explode" animation for any view inside a view group (e.g. views inside a
- * ListView). During the animation, a snapshot is taken for the view to the animated and
- * presented in a popup window or view overlay on top of the original view group. The background
- * of the view (a highlight) vertically expands (explodes) during the animation.
- * </p>
- * <p>
- * The exact implementation of the animation depends on platform API level. For JB_MR2 and later,
- * the implementation utilizes ViewOverlay to perform highly performant overlay animations; for
- * older API levels, the implementation falls back to using a full screen popup window to stage
- * the animation.
- * </p>
- * <p>
- * To start this animation, call {@link #startAnimationForView(ViewGroup, View, View, boolean, int)}
- * </p>
- */
-public class ViewGroupItemVerticalExplodeAnimation {
- /**
- * Starts a vertical explode animation for a given view situated in a given container.
- *
- * @param container the container of the view which determines the explode animation's final
- * size
- * @param viewToAnimate the view to be animated. The view will be highlighted by the explode
- * highlight, which expands from the size of the view to the size of the container.
- * @param animationStagingView the view that stages the animation. Since viewToAnimate may be
- * removed from the view tree during the animation, we need a view that'll be alive
- * for the duration of the animation so that the animation won't get cancelled.
- * @param snapshotView whether a snapshot of the view to animate is needed.
- */
- public static void startAnimationForView(final ViewGroup container, final View viewToAnimate,
- final View animationStagingView, final boolean snapshotView, final int duration) {
- if (OsUtil.isAtLeastJB_MR2() && (viewToAnimate.getContext() instanceof Activity)) {
- new ViewExplodeAnimationJellyBeanMR2(viewToAnimate, container, snapshotView, duration)
- .startAnimation();
- } else {
- // Pre JB_MR2, this animation can cause rendering failures which causes the framework
- // to fall back to software rendering where camera preview isn't supported (b/18264647)
- // just skip the animation to avoid this case.
- }
- }
-
- /**
- * Implementation class for API level >= 18.
- */
- @TargetApi(18)
- private static class ViewExplodeAnimationJellyBeanMR2 {
- private final View mViewToAnimate;
- private final ViewGroup mContainer;
- private final View mSnapshot;
- private final Bitmap mViewBitmap;
- private final int mDuration;
-
- public ViewExplodeAnimationJellyBeanMR2(final View viewToAnimate, final ViewGroup container,
- final boolean snapshotView, final int duration) {
- mViewToAnimate = viewToAnimate;
- mContainer = container;
- mDuration = duration;
- if (snapshotView) {
- mViewBitmap = snapshotView(viewToAnimate);
- mSnapshot = new View(viewToAnimate.getContext());
- } else {
- mSnapshot = null;
- mViewBitmap = null;
- }
- }
-
- public void startAnimation() {
- final Context context = mViewToAnimate.getContext();
- final Resources resources = context.getResources();
- final View decorView = ((Activity) context).getWindow().getDecorView();
- final ViewOverlay viewOverlay = decorView.getOverlay();
- if (viewOverlay instanceof ViewGroupOverlay) {
- final ViewGroupOverlay overlay = (ViewGroupOverlay) viewOverlay;
-
- // Add a shadow layer to the overlay.
- final FrameLayout shadowContainerLayer = new FrameLayout(context);
- final Drawable oldBackground = mViewToAnimate.getBackground();
- final Rect containerRect = UiUtils.getMeasuredBoundsOnScreen(mContainer);
- final Rect decorRect = UiUtils.getMeasuredBoundsOnScreen(decorView);
- // Position the container rect relative to the decor rect since the decor rect
- // defines whether the view overlay will be positioned.
- containerRect.offset(-decorRect.left, -decorRect.top);
- shadowContainerLayer.setLeft(containerRect.left);
- shadowContainerLayer.setTop(containerRect.top);
- shadowContainerLayer.setBottom(containerRect.bottom);
- shadowContainerLayer.setRight(containerRect.right);
- shadowContainerLayer.setBackgroundColor(resources.getColor(
- R.color.open_conversation_animation_background_shadow));
- // Per design request, temporarily clear out the background of the item content
- // to not show any ripple effects during animation.
- if (!(oldBackground instanceof ColorDrawable)) {
- mViewToAnimate.setBackground(null);
- }
- overlay.add(shadowContainerLayer);
-
- // Add a expand layer and position it with in the shadow background, so it can
- // be properly clipped to the container bounds during the animation.
- final View expandLayer = new View(context);
- final int elevation = resources.getDimensionPixelSize(
- R.dimen.explode_animation_highlight_elevation);
- final Rect viewRect = UiUtils.getMeasuredBoundsOnScreen(mViewToAnimate);
- // Frame viewRect from screen space to containerRect space.
- viewRect.offset(-containerRect.left - decorRect.left,
- -containerRect.top - decorRect.top);
- // Since the expand layer expands at the same rate above and below, we need to
- // compute the expand scale using the bigger of the top/bottom distances.
- final int expandLayerHalfHeight = viewRect.height() / 2;
- final int topDist = viewRect.top;
- final int bottomDist = containerRect.height() - viewRect.bottom;
- final float scale = expandLayerHalfHeight == 0 ? 1 :
- ((float) Math.max(topDist, bottomDist) + expandLayerHalfHeight) /
- expandLayerHalfHeight;
- // Position the expand layer initially to exactly match the animated item.
- shadowContainerLayer.addView(expandLayer);
- expandLayer.setLeft(viewRect.left);
- expandLayer.setTop(viewRect.top);
- expandLayer.setBottom(viewRect.bottom);
- expandLayer.setRight(viewRect.right);
- expandLayer.setBackgroundColor(resources.getColor(
- R.color.conversation_background));
- ViewCompat.setElevation(expandLayer, elevation);
-
- // Conditionally stage the snapshot in the overlay.
- if (mSnapshot != null) {
- shadowContainerLayer.addView(mSnapshot);
- mSnapshot.setLeft(viewRect.left);
- mSnapshot.setTop(viewRect.top);
- mSnapshot.setBottom(viewRect.bottom);
- mSnapshot.setRight(viewRect.right);
- mSnapshot.setBackground(new BitmapDrawable(resources, mViewBitmap));
- ViewCompat.setElevation(mSnapshot, elevation);
- }
-
- // Apply a scale animation to scale to full screen.
- expandLayer.animate().scaleY(scale)
- .setDuration(mDuration)
- .setInterpolator(UiUtils.EASE_IN_INTERPOLATOR)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- // Clean up the views added to overlay on animation finish.
- overlay.remove(shadowContainerLayer);
- mViewToAnimate.setBackground(oldBackground);
- if (mViewBitmap != null) {
- mViewBitmap.recycle();
- }
- }
- });
- }
- }
- }
-
- /**
- * Take a snapshot of the given review, return a Bitmap object that's owned by the caller.
- */
- static Bitmap snapshotView(final View view) {
- // Save the content of the view into a bitmap.
- final Bitmap viewBitmap = Bitmap.createBitmap(view.getWidth(),
- view.getHeight(), Bitmap.Config.ARGB_8888);
- // Strip the view of its background when taking a snapshot so that things like touch
- // feedback don't get accidentally snapshotted.
- final Drawable viewBackground = view.getBackground();
- ImageUtils.setBackgroundDrawableOnView(view, null);
- view.draw(new Canvas(viewBitmap));
- ImageUtils.setBackgroundDrawableOnView(view, viewBackground);
- return viewBitmap;
- }
-}
diff --git a/src/com/android/messaging/ui/appsettings/ApnEditorActivity.java b/src/com/android/messaging/ui/appsettings/ApnEditorActivity.java
deleted file mode 100644
index b7cb7ae..0000000
--- a/src/com/android/messaging/ui/appsettings/ApnEditorActivity.java
+++ /dev/null
@@ -1,463 +0,0 @@
-/*
- * 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.appsettings;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.ContentValues;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.preference.EditTextPreference;
-import android.preference.Preference;
-import android.preference.PreferenceFragment;
-import android.provider.Telephony;
-import android.support.v4.app.NavUtils;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.ApnDatabase;
-import com.android.messaging.sms.BugleApnSettingsLoader;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.PhoneUtils;
-
-public class ApnEditorActivity extends BugleActionBarActivity {
- private static final int ERROR_DIALOG_ID = 0;
- private static final String ERROR_MESSAGE_KEY = "error_msg";
- private ApnEditorFragment mApnEditorFragment;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
-
- // Display the fragment as the main content.
- mApnEditorFragment = new ApnEditorFragment();
- mApnEditorFragment.setSubId(getIntent().getIntExtra(UIIntents.UI_INTENT_EXTRA_SUB_ID,
- ParticipantData.DEFAULT_SELF_SUB_ID));
- getFragmentManager().beginTransaction()
- .replace(android.R.id.content, mApnEditorFragment)
- .commit();
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- NavUtils.navigateUpFromSameTask(this);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- protected Dialog onCreateDialog(int id, Bundle args) {
-
- if (id == ERROR_DIALOG_ID) {
- String msg = args.getString(ERROR_MESSAGE_KEY);
-
- return new AlertDialog.Builder(this)
- .setPositiveButton(android.R.string.ok, null)
- .setMessage(msg)
- .create();
- }
-
- return super.onCreateDialog(id);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_BACK: {
- if (mApnEditorFragment.validateAndSave(false)) {
- finish();
- }
- return true;
- }
- }
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
- super.onPrepareDialog(id, dialog);
-
- if (id == ERROR_DIALOG_ID) {
- final String msg = args.getString(ERROR_MESSAGE_KEY);
-
- if (msg != null) {
- ((AlertDialog) dialog).setMessage(msg);
- }
- }
- }
-
- public static class ApnEditorFragment extends PreferenceFragment implements
- SharedPreferences.OnSharedPreferenceChangeListener {
-
- private static final String SAVED_POS = "pos";
-
- private static final int MENU_DELETE = Menu.FIRST;
- private static final int MENU_SAVE = Menu.FIRST + 1;
- private static final int MENU_CANCEL = Menu.FIRST + 2;
-
- private EditTextPreference mMmsProxy;
- private EditTextPreference mMmsPort;
- private EditTextPreference mName;
- private EditTextPreference mMmsc;
- private EditTextPreference mMcc;
- private EditTextPreference mMnc;
- private static String sNotSet;
-
- private String mCurMnc;
- private String mCurMcc;
-
- private Cursor mCursor;
- private boolean mNewApn;
- private boolean mFirstTime;
- private String mCurrentId;
-
- private int mSubId;
-
- /**
- * Standard projection for the interesting columns of a normal note.
- */
- private static final String[] sProjection = new String[] {
- Telephony.Carriers._ID, // 0
- Telephony.Carriers.NAME, // 1
- Telephony.Carriers.MMSC, // 2
- Telephony.Carriers.MCC, // 3
- Telephony.Carriers.MNC, // 4
- Telephony.Carriers.NUMERIC, // 5
- Telephony.Carriers.MMSPROXY, // 6
- Telephony.Carriers.MMSPORT, // 7
- Telephony.Carriers.TYPE, // 8
- };
-
- private static final int ID_INDEX = 0;
- private static final int NAME_INDEX = 1;
- private static final int MMSC_INDEX = 2;
- private static final int MCC_INDEX = 3;
- private static final int MNC_INDEX = 4;
- private static final int NUMERIC_INDEX = 5;
- private static final int MMSPROXY_INDEX = 6;
- private static final int MMSPORT_INDEX = 7;
- private static final int TYPE_INDEX = 8;
-
- private SQLiteDatabase mDatabase;
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- addPreferencesFromResource(R.xml.apn_editor);
-
- setHasOptionsMenu(true);
-
- sNotSet = getResources().getString(R.string.apn_not_set);
- mName = (EditTextPreference) findPreference("apn_name");
- mMmsProxy = (EditTextPreference) findPreference("apn_mms_proxy");
- mMmsPort = (EditTextPreference) findPreference("apn_mms_port");
- mMmsc = (EditTextPreference) findPreference("apn_mmsc");
- mMcc = (EditTextPreference) findPreference("apn_mcc");
- mMnc = (EditTextPreference) findPreference("apn_mnc");
-
- final Intent intent = getActivity().getIntent();
-
- mFirstTime = savedInstanceState == null;
- mCurrentId = intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_APN_ROW_ID);
- mNewApn = mCurrentId == null;
-
- mDatabase = ApnDatabase.getApnDatabase().getWritableDatabase();
-
- if (mNewApn) {
- fillUi();
- } else {
- // Do initial query not on the UI thread
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- if (mCurrentId != null) {
- String selection = Telephony.Carriers._ID + " =?";
- String[] selectionArgs = new String[]{ mCurrentId };
- mCursor = mDatabase.query(ApnDatabase.APN_TABLE, sProjection, selection,
- selectionArgs, null, null, null, null);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- if (mCursor == null) {
- getActivity().finish();
- return;
- }
- mCursor.moveToFirst();
-
- fillUi();
- }
- }.execute((Void) null);
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (mCursor != null) {
- mCursor.close();
- mCursor = null;
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
- getPreferenceScreen().getSharedPreferences()
- .registerOnSharedPreferenceChangeListener(this);
- }
-
- @Override
- public void onPause() {
- getPreferenceScreen().getSharedPreferences()
- .unregisterOnSharedPreferenceChangeListener(this);
- super.onPause();
- }
-
- public void setSubId(final int subId) {
- mSubId = subId;
- }
-
- private void fillUi() {
- if (mNewApn) {
- mMcc.setText(null);
- mMnc.setText(null);
- String numeric = PhoneUtils.get(mSubId).getSimOperatorNumeric();
- // MCC is first 3 chars and then in 2 - 3 chars of MNC
- if (numeric != null && numeric.length() > 4) {
- // Country code
- String mcc = numeric.substring(0, 3);
- // Network code
- String mnc = numeric.substring(3);
- // Auto populate MNC and MCC for new entries, based on what SIM reports
- mMcc.setText(mcc);
- mMnc.setText(mnc);
- mCurMnc = mnc;
- mCurMcc = mcc;
- }
- mName.setText(null);
- mMmsProxy.setText(null);
- mMmsPort.setText(null);
- mMmsc.setText(null);
- } else if (mFirstTime) {
- mFirstTime = false;
- // Fill in all the values from the db in both text editor and summary
- mName.setText(mCursor.getString(NAME_INDEX));
- mMmsProxy.setText(mCursor.getString(MMSPROXY_INDEX));
- mMmsPort.setText(mCursor.getString(MMSPORT_INDEX));
- mMmsc.setText(mCursor.getString(MMSC_INDEX));
- mMcc.setText(mCursor.getString(MCC_INDEX));
- mMnc.setText(mCursor.getString(MNC_INDEX));
- }
-
- mName.setSummary(checkNull(mName.getText()));
- mMmsProxy.setSummary(checkNull(mMmsProxy.getText()));
- mMmsPort.setSummary(checkNull(mMmsPort.getText()));
- mMmsc.setSummary(checkNull(mMmsc.getText()));
- mMcc.setSummary(checkNull(mMcc.getText()));
- mMnc.setSummary(checkNull(mMnc.getText()));
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- // If it's a new APN, then cancel will delete the new entry in onPause
- if (!mNewApn) {
- menu.add(0, MENU_DELETE, 0, R.string.menu_delete_apn)
- .setIcon(R.drawable.ic_delete_small_dark);
- }
- menu.add(0, MENU_SAVE, 0, R.string.menu_save_apn)
- .setIcon(android.R.drawable.ic_menu_save);
- menu.add(0, MENU_CANCEL, 0, R.string.menu_discard_apn_change)
- .setIcon(android.R.drawable.ic_menu_close_clear_cancel);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case MENU_DELETE:
- deleteApn();
- return true;
-
- case MENU_SAVE:
- if (validateAndSave(false)) {
- getActivity().finish();
- }
- return true;
-
- case MENU_CANCEL:
- getActivity().finish();
- return true;
-
- case android.R.id.home:
- getActivity().onBackPressed();
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public void onSaveInstanceState(Bundle icicle) {
- super.onSaveInstanceState(icicle);
- if (validateAndSave(true) && mCursor != null) {
- icicle.putInt(SAVED_POS, mCursor.getInt(ID_INDEX));
- }
- }
-
- /**
- * Check the key fields' validity and save if valid.
- * @param force save even if the fields are not valid, if the app is
- * being suspended
- * @return true if the data was saved
- */
- private boolean validateAndSave(boolean force) {
- final String name = checkNotSet(mName.getText());
- final String mcc = checkNotSet(mMcc.getText());
- final String mnc = checkNotSet(mMnc.getText());
-
- if (getErrorMsg() != null && !force) {
- final Bundle bundle = new Bundle();
- bundle.putString(ERROR_MESSAGE_KEY, getErrorMsg());
- getActivity().showDialog(ERROR_DIALOG_ID, bundle);
- return false;
- }
-
- // Make database changes not on the UI thread
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- ContentValues values = new ContentValues();
-
- // Add a dummy name "Untitled", if the user exits the screen without adding a
- // name but entered other information worth keeping.
- values.put(Telephony.Carriers.NAME, name.length() < 1 ?
- getResources().getString(R.string.untitled_apn) : name);
- values.put(Telephony.Carriers.MMSPROXY, checkNotSet(mMmsProxy.getText()));
- values.put(Telephony.Carriers.MMSPORT, checkNotSet(mMmsPort.getText()));
- values.put(Telephony.Carriers.MMSC, checkNotSet(mMmsc.getText()));
-
- values.put(Telephony.Carriers.TYPE, BugleApnSettingsLoader.APN_TYPE_MMS);
-
- values.put(Telephony.Carriers.MCC, mcc);
- values.put(Telephony.Carriers.MNC, mnc);
-
- values.put(Telephony.Carriers.NUMERIC, mcc + mnc);
-
- if (mCurMnc != null && mCurMcc != null) {
- if (mCurMnc.equals(mnc) && mCurMcc.equals(mcc)) {
- values.put(Telephony.Carriers.CURRENT, 1);
- }
- }
-
- if (mNewApn) {
- mDatabase.insert(ApnDatabase.APN_TABLE, null, values);
- } else {
- // update the APN
- String selection = Telephony.Carriers._ID + " =?";
- String[] selectionArgs = new String[]{ mCurrentId };
- int updated = mDatabase.update(ApnDatabase.APN_TABLE, values,
- selection, selectionArgs);
- }
- return null;
- }
- }.execute((Void) null);
-
- return true;
- }
-
- private void deleteApn() {
- // Make database changes not on the UI thread
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- // delete the APN
- String where = Telephony.Carriers._ID + " =?";
- String[] whereArgs = new String[]{ mCurrentId };
-
- mDatabase.delete(ApnDatabase.APN_TABLE, where, whereArgs);
- return null;
- }
- }.execute((Void) null);
-
- getActivity().finish();
- }
-
- private String checkNull(String value) {
- if (value == null || value.length() == 0) {
- return sNotSet;
- } else {
- return value;
- }
- }
-
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- Preference pref = findPreference(key);
- if (pref != null) {
- pref.setSummary(checkNull(sharedPreferences.getString(key, "")));
- }
- }
-
- private String getErrorMsg() {
- String errorMsg = null;
-
- String name = checkNotSet(mName.getText());
- String mcc = checkNotSet(mMcc.getText());
- String mnc = checkNotSet(mMnc.getText());
-
- if (name.length() < 1) {
- errorMsg = getString(R.string.error_apn_name_empty);
- } else if (mcc.length() != 3) {
- errorMsg = getString(R.string.error_mcc_not3);
- } else if ((mnc.length() & 0xFFFE) != 2) {
- errorMsg = getString(R.string.error_mnc_not23);
- }
-
- return errorMsg;
- }
-
- private String checkNotSet(String value) {
- if (value == null || value.equals(sNotSet)) {
- return "";
- } else {
- return value;
- }
- }
- }
-}
diff --git a/src/com/android/messaging/ui/appsettings/ApnPreference.java b/src/com/android/messaging/ui/appsettings/ApnPreference.java
deleted file mode 100644
index 74c6a08..0000000
--- a/src/com/android/messaging/ui/appsettings/ApnPreference.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * 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.appsettings;
-
-import android.content.Context;
-import android.preference.Preference;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.CompoundButton;
-import android.widget.RadioButton;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.ui.UIIntents;
-
-/**
- * ApnPreference implements a pref, typically used as a list item, that has a title/summary on
- * the left and a radio button on the right.
- *
- */
-public class ApnPreference extends Preference implements
- CompoundButton.OnCheckedChangeListener, OnClickListener {
- static final String TAG = "ApnPreference";
-
- public ApnPreference(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
-
- public ApnPreference(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.apnPreferenceStyle);
- }
-
- public ApnPreference(Context context) {
- this(context, null);
- }
-
- private static String mSelectedKey = null;
- private static CompoundButton mCurrentChecked = null;
- private boolean mProtectFromCheckedChange = false;
- private boolean mSelectable = true;
- private int mSubId = ParticipantData.DEFAULT_SELF_SUB_ID;
-
- @Override
- public View getView(View convertView, ViewGroup parent) {
- View view = super.getView(convertView, parent);
-
- View widget = view.findViewById(R.id.apn_radiobutton);
- if ((widget != null) && widget instanceof RadioButton) {
- RadioButton rb = (RadioButton) widget;
- if (mSelectable) {
- rb.setOnCheckedChangeListener(this);
-
- boolean isChecked = getKey().equals(mSelectedKey);
- if (isChecked) {
- mCurrentChecked = rb;
- mSelectedKey = getKey();
- }
-
- mProtectFromCheckedChange = true;
- rb.setChecked(isChecked);
- mProtectFromCheckedChange = false;
- } else {
- rb.setVisibility(View.GONE);
- }
- setApnRadioButtonContentDescription(rb);
- }
-
- View textLayout = view.findViewById(R.id.text_layout);
- if ((textLayout != null) && textLayout instanceof RelativeLayout) {
- textLayout.setOnClickListener(this);
- }
-
- return view;
- }
-
- public void setApnRadioButtonContentDescription(final CompoundButton buttonView) {
- final View widget = (View) buttonView.getParent();
- final TextView tv = (TextView) widget.findViewById(android.R.id.title);
- final String apnTitle = tv.getText().toString();
- buttonView.setContentDescription(apnTitle);
- }
-
- public boolean isChecked() {
- return getKey().equals(mSelectedKey);
- }
-
- public void setChecked() {
- mSelectedKey = getKey();
- }
-
- public void setSubId(final int subId) {
- mSubId = subId;
- }
-
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- Log.i(TAG, "ID: " + getKey() + " :" + isChecked);
- if (mProtectFromCheckedChange) {
- return;
- }
-
- if (isChecked) {
- if (mCurrentChecked != null) {
- mCurrentChecked.setChecked(false);
- }
- mCurrentChecked = buttonView;
- mSelectedKey = getKey();
- callChangeListener(mSelectedKey);
- } else {
- mCurrentChecked = null;
- mSelectedKey = null;
- }
- setApnRadioButtonContentDescription(buttonView);
- }
-
- public void onClick(android.view.View v) {
- if ((v != null) && (R.id.text_layout == v.getId())) {
- Context context = getContext();
- if (context != null) {
- context.startActivity(
- UIIntents.get().getApnEditorIntent(context, getKey(), mSubId));
- }
- }
- }
-
- public void setSelectable(boolean selectable) {
- mSelectable = selectable;
- }
-
- public boolean getSelectable() {
- return mSelectable;
- }
-}
diff --git a/src/com/android/messaging/ui/appsettings/ApnSettingsActivity.java b/src/com/android/messaging/ui/appsettings/ApnSettingsActivity.java
deleted file mode 100644
index 28dfc2a..0000000
--- a/src/com/android/messaging/ui/appsettings/ApnSettingsActivity.java
+++ /dev/null
@@ -1,406 +0,0 @@
-/*
- * 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.appsettings;
-
-import android.app.Activity;
-import android.app.Dialog;
-import android.app.ProgressDialog;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.UserManager;
-import android.preference.Preference;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceGroup;
-import android.preference.PreferenceScreen;
-import android.provider.Telephony;
-import android.support.v4.app.NavUtils;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.widget.ListView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.ApnDatabase;
-import com.android.messaging.sms.BugleApnSettingsLoader;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-
-public class ApnSettingsActivity extends BugleActionBarActivity {
- private static final int DIALOG_RESTORE_DEFAULTAPN = 1001;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
-
- // Display the fragment as the main content.
- final ApnSettingsFragment fragment = new ApnSettingsFragment();
- fragment.setSubId(getIntent().getIntExtra(UIIntents.UI_INTENT_EXTRA_SUB_ID,
- ParticipantData.DEFAULT_SELF_SUB_ID));
- getFragmentManager().beginTransaction()
- .replace(android.R.id.content, fragment)
- .commit();
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- NavUtils.navigateUpFromSameTask(this);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- protected Dialog onCreateDialog(int id) {
- if (id == DIALOG_RESTORE_DEFAULTAPN) {
- ProgressDialog dialog = new ProgressDialog(this);
- dialog.setMessage(getResources().getString(R.string.restore_default_apn));
- dialog.setCancelable(false);
- return dialog;
- }
- return null;
- }
-
- public static class ApnSettingsFragment extends PreferenceFragment implements
- Preference.OnPreferenceChangeListener {
- public static final String EXTRA_POSITION = "position";
-
- public static final String APN_ID = "apn_id";
-
- private static final String[] APN_PROJECTION = {
- Telephony.Carriers._ID, // 0
- Telephony.Carriers.NAME, // 1
- Telephony.Carriers.APN, // 2
- Telephony.Carriers.TYPE // 3
- };
- private static final int ID_INDEX = 0;
- private static final int NAME_INDEX = 1;
- private static final int APN_INDEX = 2;
- private static final int TYPES_INDEX = 3;
-
- private static final int MENU_NEW = Menu.FIRST;
- private static final int MENU_RESTORE = Menu.FIRST + 1;
-
- private static final int EVENT_RESTORE_DEFAULTAPN_START = 1;
- private static final int EVENT_RESTORE_DEFAULTAPN_COMPLETE = 2;
-
- private static boolean mRestoreDefaultApnMode;
-
- private RestoreApnUiHandler mRestoreApnUiHandler;
- private RestoreApnProcessHandler mRestoreApnProcessHandler;
- private HandlerThread mRestoreDefaultApnThread;
-
- private String mSelectedKey;
-
- private static final ContentValues sCurrentNullMap;
- private static final ContentValues sCurrentSetMap;
-
- private UserManager mUm;
-
- private boolean mUnavailable;
- private int mSubId;
-
- static {
- sCurrentNullMap = new ContentValues(1);
- sCurrentNullMap.putNull(Telephony.Carriers.CURRENT);
-
- sCurrentSetMap = new ContentValues(1);
- sCurrentSetMap.put(Telephony.Carriers.CURRENT, "2"); // 2 for user-selected APN,
- // 1 for Bugle-selected APN
- }
-
- private SQLiteDatabase mDatabase;
-
- public void setSubId(final int subId) {
- mSubId = subId;
- }
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- mDatabase = ApnDatabase.getApnDatabase().getWritableDatabase();
-
- if (OsUtil.isAtLeastL()) {
- mUm = (UserManager) getActivity().getSystemService(Context.USER_SERVICE);
- if (!mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
- setHasOptionsMenu(true);
- }
- } else {
- setHasOptionsMenu(true);
- }
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- final ListView lv = (ListView) getView().findViewById(android.R.id.list);
- TextView empty = (TextView) getView().findViewById(android.R.id.empty);
- if (empty != null) {
- empty.setText(R.string.apn_settings_not_available);
- lv.setEmptyView(empty);
- }
-
- if (OsUtil.isAtLeastL() &&
- mUm.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS)) {
- mUnavailable = true;
- setPreferenceScreen(getPreferenceManager().createPreferenceScreen(getActivity()));
- return;
- }
-
- addPreferencesFromResource(R.xml.apn_settings);
-
- lv.setItemsCanFocus(true);
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- if (mUnavailable) {
- return;
- }
-
- if (!mRestoreDefaultApnMode) {
- fillList();
- }
- }
-
- @Override
- public void onPause() {
- super.onPause();
-
- if (mUnavailable) {
- return;
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
-
- if (mRestoreDefaultApnThread != null) {
- mRestoreDefaultApnThread.quit();
- }
- }
-
- private void fillList() {
- final String mccMnc = PhoneUtils.getMccMncString(PhoneUtils.get(mSubId).getMccMnc());
-
- new AsyncTask<Void, Void, Cursor>() {
- @Override
- protected Cursor doInBackground(Void... params) {
- String selection = Telephony.Carriers.NUMERIC + " =?";
- String[] selectionArgs = new String[]{ mccMnc };
- final Cursor cursor = mDatabase.query(ApnDatabase.APN_TABLE, APN_PROJECTION,
- selection, selectionArgs, null, null, null, null);
- return cursor;
- }
-
- @Override
- protected void onPostExecute(Cursor cursor) {
- if (cursor != null) {
- try {
- PreferenceGroup apnList = (PreferenceGroup)
- findPreference(getString(R.string.apn_list_pref_key));
- apnList.removeAll();
-
- mSelectedKey = BugleApnSettingsLoader.getFirstTryApn(mDatabase, mccMnc);
- while (cursor.moveToNext()) {
- String name = cursor.getString(NAME_INDEX);
- String apn = cursor.getString(APN_INDEX);
- String key = cursor.getString(ID_INDEX);
- String type = cursor.getString(TYPES_INDEX);
-
- if (BugleApnSettingsLoader.isValidApnType(type,
- BugleApnSettingsLoader.APN_TYPE_MMS)) {
- ApnPreference pref = new ApnPreference(getActivity());
- pref.setKey(key);
- pref.setTitle(name);
- pref.setSummary(apn);
- pref.setPersistent(false);
- pref.setOnPreferenceChangeListener(ApnSettingsFragment.this);
- pref.setSelectable(true);
-
- // Turn on the radio button for the currently selected APN. If
- // there is no selected APN, don't select an APN.
- if ((mSelectedKey != null && mSelectedKey.equals(key))) {
- pref.setChecked();
- }
- apnList.addPreference(pref);
- }
- }
- } finally {
- cursor.close();
- }
- }
- }
- }.execute((Void) null);
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- if (!mUnavailable) {
- menu.add(0, MENU_NEW, 0,
- getResources().getString(R.string.menu_new_apn))
- .setIcon(R.drawable.ic_add_gray)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- menu.add(0, MENU_RESTORE, 0,
- getResources().getString(R.string.menu_restore_default_apn))
- .setIcon(android.R.drawable.ic_menu_upload);
- }
-
- super.onCreateOptionsMenu(menu, inflater);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case MENU_NEW:
- addNewApn();
- return true;
-
- case MENU_RESTORE:
- restoreDefaultApn();
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- private void addNewApn() {
- startActivity(UIIntents.get().getApnEditorIntent(getActivity(), null, mSubId));
- }
-
- @Override
- public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
- Preference preference) {
- startActivity(
- UIIntents.get().getApnEditorIntent(getActivity(), preference.getKey(), mSubId));
- return true;
- }
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- if (newValue instanceof String) {
- setSelectedApnKey((String) newValue);
- }
-
- return true;
- }
-
- // current=2 means user selected APN
- private static final String UPDATE_SELECTION = Telephony.Carriers.CURRENT + " =?";
- private static final String[] UPDATE_SELECTION_ARGS = new String[] { "2" };
- private void setSelectedApnKey(final String key) {
- mSelectedKey = key;
-
- // Make database changes not on the UI thread
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- // null out the previous "current=2" APN
- mDatabase.update(ApnDatabase.APN_TABLE, sCurrentNullMap,
- UPDATE_SELECTION, UPDATE_SELECTION_ARGS);
-
- // set the new "current" APN (2)
- String selection = Telephony.Carriers._ID + " =?";
- String[] selectionArgs = new String[]{ key };
-
- mDatabase.update(ApnDatabase.APN_TABLE, sCurrentSetMap,
- selection, selectionArgs);
- return null;
- }
- }.execute((Void) null);
- }
-
- private boolean restoreDefaultApn() {
- getActivity().showDialog(DIALOG_RESTORE_DEFAULTAPN);
- mRestoreDefaultApnMode = true;
-
- if (mRestoreApnUiHandler == null) {
- mRestoreApnUiHandler = new RestoreApnUiHandler();
- }
-
- if (mRestoreApnProcessHandler == null ||
- mRestoreDefaultApnThread == null) {
- mRestoreDefaultApnThread = new HandlerThread(
- "Restore default APN Handler: Process Thread");
- mRestoreDefaultApnThread.start();
- mRestoreApnProcessHandler = new RestoreApnProcessHandler(
- mRestoreDefaultApnThread.getLooper(), mRestoreApnUiHandler);
- }
-
- mRestoreApnProcessHandler.sendEmptyMessage(EVENT_RESTORE_DEFAULTAPN_START);
- return true;
- }
-
- private class RestoreApnUiHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case EVENT_RESTORE_DEFAULTAPN_COMPLETE:
- fillList();
- getPreferenceScreen().setEnabled(true);
- mRestoreDefaultApnMode = false;
- final Activity activity = getActivity();
- activity.dismissDialog(DIALOG_RESTORE_DEFAULTAPN);
- Toast.makeText(activity, getResources().getString(
- R.string.restore_default_apn_completed), Toast.LENGTH_LONG)
- .show();
- break;
- }
- }
- }
-
- private class RestoreApnProcessHandler extends Handler {
- private Handler mCachedRestoreApnUiHandler;
-
- public RestoreApnProcessHandler(Looper looper, Handler restoreApnUiHandler) {
- super(looper);
- this.mCachedRestoreApnUiHandler = restoreApnUiHandler;
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case EVENT_RESTORE_DEFAULTAPN_START:
- ApnDatabase.forceBuildAndLoadApnTables();
- mCachedRestoreApnUiHandler.sendEmptyMessage(
- EVENT_RESTORE_DEFAULTAPN_COMPLETE);
- break;
- }
- }
- }
- }
-}
diff --git a/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java b/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java
deleted file mode 100644
index 906009f..0000000
--- a/src/com/android/messaging/ui/appsettings/ApplicationSettingsActivity.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * 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.appsettings;
-
-import android.app.FragmentTransaction;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.preference.Preference;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceScreen;
-import android.preference.RingtonePreference;
-import android.preference.TwoStatePreference;
-import android.provider.Settings;
-import android.support.v4.app.NavUtils;
-import android.text.TextUtils;
-import android.view.Menu;
-import android.view.MenuItem;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.LicenseActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.util.DebugUtils;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-
-public class ApplicationSettingsActivity extends BugleActionBarActivity {
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- final boolean topLevel = getIntent().getBooleanExtra(
- UIIntents.UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS, false);
- if (topLevel) {
- getSupportActionBar().setTitle(getString(R.string.settings_activity_title));
- }
-
- FragmentTransaction ft = getFragmentManager().beginTransaction();
- ft.replace(android.R.id.content, new ApplicationSettingsFragment());
- ft.commit();
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- if (super.onCreateOptionsMenu(menu)) {
- return true;
- }
- getMenuInflater().inflate(R.menu.settings_menu, menu);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- NavUtils.navigateUpFromSameTask(this);
- return true;
- case R.id.action_license:
- final Intent intent = new Intent(this, LicenseActivity.class);
- startActivity(intent);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- public static class ApplicationSettingsFragment extends PreferenceFragment implements
- OnSharedPreferenceChangeListener {
-
- private String mNotificationsEnabledPreferenceKey;
- private TwoStatePreference mNotificationsEnabledPreference;
- private String mRingtonePreferenceKey;
- private RingtonePreference mRingtonePreference;
- private Preference mVibratePreference;
- private String mSmsDisabledPrefKey;
- private Preference mSmsDisabledPreference;
- private String mSmsEnabledPrefKey;
- private Preference mSmsEnabledPreference;
- private boolean mIsSmsPreferenceClicked;
-
- public ApplicationSettingsFragment() {
- // Required empty constructor
- }
-
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- getPreferenceManager().setSharedPreferencesName(BuglePrefs.SHARED_PREFERENCES_NAME);
- addPreferencesFromResource(R.xml.preferences_application);
-
- mNotificationsEnabledPreferenceKey =
- getString(R.string.notifications_enabled_pref_key);
- mNotificationsEnabledPreference = (TwoStatePreference) findPreference(
- mNotificationsEnabledPreferenceKey);
- mRingtonePreferenceKey = getString(R.string.notification_sound_pref_key);
- mRingtonePreference = (RingtonePreference) findPreference(mRingtonePreferenceKey);
- mVibratePreference = findPreference(
- getString(R.string.notification_vibration_pref_key));
- mSmsDisabledPrefKey = getString(R.string.sms_disabled_pref_key);
- mSmsDisabledPreference = findPreference(mSmsDisabledPrefKey);
- mSmsEnabledPrefKey = getString(R.string.sms_enabled_pref_key);
- mSmsEnabledPreference = findPreference(mSmsEnabledPrefKey);
- mIsSmsPreferenceClicked = false;
-
- final SharedPreferences prefs = getPreferenceScreen().getSharedPreferences();
- updateSoundSummary(prefs);
-
- if (!DebugUtils.isDebugEnabled()) {
- final Preference debugCategory = findPreference(getString(
- R.string.debug_pref_key));
- getPreferenceScreen().removePreference(debugCategory);
- }
-
- final PreferenceScreen advancedScreen = (PreferenceScreen) findPreference(
- getString(R.string.advanced_pref_key));
- final boolean topLevel = getActivity().getIntent().getBooleanExtra(
- UIIntents.UI_INTENT_EXTRA_TOP_LEVEL_SETTINGS, false);
- if (topLevel) {
- advancedScreen.setIntent(UIIntents.get()
- .getAdvancedSettingsIntent(getPreferenceScreen().getContext()));
- } else {
- // Hide the Advanced settings screen if this is not top-level; these are shown at
- // the parent SettingsActivity.
- getPreferenceScreen().removePreference(advancedScreen);
- }
- }
-
- @Override
- public boolean onPreferenceTreeClick (PreferenceScreen preferenceScreen,
- Preference preference) {
- if (preference.getKey() == mSmsDisabledPrefKey ||
- preference.getKey() == mSmsEnabledPrefKey) {
- mIsSmsPreferenceClicked = true;
- }
- return super.onPreferenceTreeClick(preferenceScreen, preference);
- }
-
- private void updateSoundSummary(final SharedPreferences sharedPreferences) {
- // The silent ringtone just returns an empty string
- String ringtoneName = mRingtonePreference.getContext().getString(
- R.string.silent_ringtone);
-
- String ringtoneString = sharedPreferences.getString(mRingtonePreferenceKey, null);
-
- // Bootstrap the default setting in the preferences so that we have a valid selection
- // in the dialog the first time that the user opens it.
- if (ringtoneString == null) {
- ringtoneString = Settings.System.DEFAULT_NOTIFICATION_URI.toString();
- final SharedPreferences.Editor editor = sharedPreferences.edit();
- editor.putString(mRingtonePreferenceKey, ringtoneString);
- editor.apply();
- }
-
- if (!TextUtils.isEmpty(ringtoneString)) {
- final Uri ringtoneUri = Uri.parse(ringtoneString);
- final Ringtone tone = RingtoneManager.getRingtone(mRingtonePreference.getContext(),
- ringtoneUri);
-
- if (tone != null) {
- ringtoneName = tone.getTitle(mRingtonePreference.getContext());
- }
- }
-
- mRingtonePreference.setSummary(ringtoneName);
- }
-
- private void updateSmsEnabledPreferences() {
- if (!OsUtil.isAtLeastKLP()) {
- getPreferenceScreen().removePreference(mSmsDisabledPreference);
- getPreferenceScreen().removePreference(mSmsEnabledPreference);
- } else {
- final String defaultSmsAppLabel = getString(R.string.default_sms_app,
- PhoneUtils.getDefault().getDefaultSmsAppLabel());
- boolean isSmsEnabledBeforeState;
- boolean isSmsEnabledCurrentState;
- if (PhoneUtils.getDefault().isDefaultSmsApp()) {
- if (getPreferenceScreen().findPreference(mSmsEnabledPrefKey) == null) {
- getPreferenceScreen().addPreference(mSmsEnabledPreference);
- isSmsEnabledBeforeState = false;
- } else {
- isSmsEnabledBeforeState = true;
- }
- isSmsEnabledCurrentState = true;
- getPreferenceScreen().removePreference(mSmsDisabledPreference);
- mSmsEnabledPreference.setSummary(defaultSmsAppLabel);
- } else {
- if (getPreferenceScreen().findPreference(mSmsDisabledPrefKey) == null) {
- getPreferenceScreen().addPreference(mSmsDisabledPreference);
- isSmsEnabledBeforeState = true;
- } else {
- isSmsEnabledBeforeState = false;
- }
- isSmsEnabledCurrentState = false;
- getPreferenceScreen().removePreference(mSmsEnabledPreference);
- mSmsDisabledPreference.setSummary(defaultSmsAppLabel);
- }
- updateNotificationsPreferences();
- }
- mIsSmsPreferenceClicked = false;
- }
-
- private void updateNotificationsPreferences() {
- final boolean canNotify = !OsUtil.isAtLeastKLP()
- || PhoneUtils.getDefault().isDefaultSmsApp();
- mNotificationsEnabledPreference.setEnabled(canNotify);
- }
-
- @Override
- public void onStart() {
- super.onStart();
- // We do this on start rather than on resume because the sound picker is in a
- // separate activity.
- getPreferenceScreen().getSharedPreferences()
- .registerOnSharedPreferenceChangeListener(this);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- updateSmsEnabledPreferences();
- updateNotificationsPreferences();
- }
-
- @Override
- public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
- final String key) {
- if (key.equals(mNotificationsEnabledPreferenceKey)) {
- updateNotificationsPreferences();
- } else if (key.equals(mRingtonePreferenceKey)) {
- updateSoundSummary(sharedPreferences);
- }
- }
-
- @Override
- public void onStop() {
- super.onStop();
- getPreferenceScreen().getSharedPreferences()
- .unregisterOnSharedPreferenceChangeListener(this);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/appsettings/GroupMmsSettingDialog.java b/src/com/android/messaging/ui/appsettings/GroupMmsSettingDialog.java
deleted file mode 100644
index 739d2dc..0000000
--- a/src/com/android/messaging/ui/appsettings/GroupMmsSettingDialog.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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.appsettings;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.RadioButton;
-
-import com.android.messaging.R;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BuglePrefs;
-
-/**
- * Displays an on/off switch for group MMS setting for a given subscription.
- */
-public class GroupMmsSettingDialog {
- private final Context mContext;
- private final int mSubId;
- private AlertDialog mDialog;
-
- /**
- * Shows a new group MMS setting dialog.
- */
- public static void showDialog(final Context context, final int subId) {
- new GroupMmsSettingDialog(context, subId).show();
- }
-
- private GroupMmsSettingDialog(final Context context, final int subId) {
- mContext = context;
- mSubId = subId;
- }
-
- private void show() {
- Assert.isNull(mDialog);
- mDialog = new AlertDialog.Builder(mContext)
- .setView(createView())
- .setTitle(R.string.group_mms_pref_title)
- .setNegativeButton(android.R.string.cancel, null)
- .show();
- }
-
- private void changeGroupMmsSettings(final boolean enable) {
- Assert.notNull(mDialog);
- BuglePrefs.getSubscriptionPrefs(mSubId).putBoolean(
- mContext.getString(R.string.group_mms_pref_key), enable);
- mDialog.dismiss();
- }
-
- private View createView() {
- final LayoutInflater inflater = (LayoutInflater) mContext
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- final View rootView = inflater.inflate(R.layout.group_mms_setting_dialog, null, false);
- final RadioButton disableButton = (RadioButton)
- rootView.findViewById(R.id.disable_group_mms_button);
- final RadioButton enableButton = (RadioButton)
- rootView.findViewById(R.id.enable_group_mms_button);
- disableButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- changeGroupMmsSettings(false);
- }
- });
- enableButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- changeGroupMmsSettings(true);
- }
- });
- final boolean mmsEnabled = BuglePrefs.getSubscriptionPrefs(mSubId).getBoolean(
- mContext.getString(R.string.group_mms_pref_key),
- mContext.getResources().getBoolean(R.bool.group_mms_pref_default));
- enableButton.setChecked(mmsEnabled);
- disableButton.setChecked(!mmsEnabled);
- return rootView;
- }
-}
diff --git a/src/com/android/messaging/ui/appsettings/PerSubscriptionSettingsActivity.java b/src/com/android/messaging/ui/appsettings/PerSubscriptionSettingsActivity.java
deleted file mode 100644
index e02823f..0000000
--- a/src/com/android/messaging/ui/appsettings/PerSubscriptionSettingsActivity.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * 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.appsettings;
-
-import android.app.FragmentTransaction;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.preference.Preference;
-import android.preference.Preference.OnPreferenceClickListener;
-import android.preference.PreferenceCategory;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceScreen;
-import android.support.v4.app.NavUtils;
-import android.text.TextUtils;
-import android.view.MenuItem;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.ParticipantRefresh;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.ApnDatabase;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.PhoneUtils;
-
-public class PerSubscriptionSettingsActivity extends BugleActionBarActivity {
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- final String title = getIntent().getStringExtra(
- UIIntents.UI_INTENT_EXTRA_PER_SUBSCRIPTION_SETTING_TITLE);
- if (!TextUtils.isEmpty(title)) {
- getSupportActionBar().setTitle(title);
- } else {
- // This will fall back to the default title, i.e. "Messaging settings," so No-op.
- }
-
- final FragmentTransaction ft = getFragmentManager().beginTransaction();
- final PerSubscriptionSettingsFragment fragment = new PerSubscriptionSettingsFragment();
- ft.replace(android.R.id.content, fragment);
- ft.commit();
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- NavUtils.navigateUpFromSameTask(this);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- public static class PerSubscriptionSettingsFragment extends PreferenceFragment
- implements OnSharedPreferenceChangeListener {
- private PhoneNumberPreference mPhoneNumberPreference;
- private Preference mGroupMmsPreference;
- private String mGroupMmsPrefKey;
- private String mPhoneNumberKey;
- private int mSubId;
-
- public PerSubscriptionSettingsFragment() {
- // Required empty constructor
- }
-
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- // Get sub id from launch intent
- final Intent intent = getActivity().getIntent();
- Assert.notNull(intent);
- mSubId = (intent != null) ? intent.getIntExtra(UIIntents.UI_INTENT_EXTRA_SUB_ID,
- ParticipantData.DEFAULT_SELF_SUB_ID) : ParticipantData.DEFAULT_SELF_SUB_ID;
-
- final BuglePrefs subPrefs = Factory.get().getSubscriptionPrefs(mSubId);
- getPreferenceManager().setSharedPreferencesName(subPrefs.getSharedPreferencesName());
- addPreferencesFromResource(R.xml.preferences_per_subscription);
-
- mPhoneNumberKey = getString(R.string.mms_phone_number_pref_key);
- mPhoneNumberPreference = (PhoneNumberPreference) findPreference(mPhoneNumberKey);
- final PreferenceCategory advancedCategory = (PreferenceCategory)
- findPreference(getString(R.string.advanced_category_pref_key));
- final PreferenceCategory mmsCategory = (PreferenceCategory)
- findPreference(getString(R.string.mms_messaging_category_pref_key));
-
- mPhoneNumberPreference.setDefaultPhoneNumber(
- PhoneUtils.get(mSubId).getCanonicalForSelf(false/*allowOverride*/), mSubId);
-
- mGroupMmsPrefKey = getString(R.string.group_mms_pref_key);
- mGroupMmsPreference = findPreference(mGroupMmsPrefKey);
- if (!MmsConfig.get(mSubId).getGroupMmsEnabled()) {
- // Always show group messaging setting even if the SIM has no number
- // If broadcast sms is selected, the SIM number is not needed
- // If group mms is selected, the phone number dialog will popup when message
- // is being sent, making sure we will have a self number for group mms.
- mmsCategory.removePreference(mGroupMmsPreference);
- } else {
- mGroupMmsPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference pref) {
- GroupMmsSettingDialog.showDialog(getActivity(), mSubId);
- return true;
- }
- });
- updateGroupMmsPrefSummary();
- }
-
- if (!MmsConfig.get(mSubId).getSMSDeliveryReportsEnabled()) {
- final Preference deliveryReportsPref = findPreference(
- getString(R.string.delivery_reports_pref_key));
- mmsCategory.removePreference(deliveryReportsPref);
- }
- final Preference wirelessAlertPref = findPreference(getString(
- R.string.wireless_alerts_key));
- if (!isCellBroadcastAppLinkEnabled()) {
- advancedCategory.removePreference(wirelessAlertPref);
- } else {
- wirelessAlertPref.setOnPreferenceClickListener(
- new Preference.OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(final Preference preference) {
- try {
- startActivity(UIIntents.get().getWirelessAlertsIntent());
- } catch (final ActivityNotFoundException e) {
- // Handle so we shouldn't crash if the wireless alerts
- // implementation is broken.
- LogUtil.e(LogUtil.BUGLE_TAG,
- "Failed to launch wireless alerts activity", e);
- }
- return true;
- }
- });
- }
-
- // Access Point Names (APNs)
- final Preference apnsPref = findPreference(getString(R.string.sms_apns_key));
-
- if (MmsUtils.useSystemApnTable() && !ApnDatabase.doesDatabaseExist()) {
- // Don't remove the ability to edit the local APN prefs if this device lets us
- // access the system APN, but we can't find the MCC/MNC in the APN table and we
- // created the local APN table in case the MCC/MNC was in there. In other words,
- // if the local APN table exists, let the user edit it.
- advancedCategory.removePreference(apnsPref);
- } else {
- final PreferenceScreen apnsScreen = (PreferenceScreen) findPreference(
- getString(R.string.sms_apns_key));
- apnsScreen.setIntent(UIIntents.get()
- .getApnSettingsIntent(getPreferenceScreen().getContext(), mSubId));
- }
-
- // We want to disable preferences if we are not the default app, but we do all of the
- // above first so that the user sees the correct information on the screen
- if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
- mGroupMmsPreference.setEnabled(false);
- final Preference autoRetrieveMmsPreference =
- findPreference(getString(R.string.auto_retrieve_mms_pref_key));
- autoRetrieveMmsPreference.setEnabled(false);
- final Preference deliveryReportsPreference =
- findPreference(getString(R.string.delivery_reports_pref_key));
- deliveryReportsPreference.setEnabled(false);
- }
- }
-
- private boolean isCellBroadcastAppLinkEnabled() {
- if (!MmsConfig.get(mSubId).getShowCellBroadcast()) {
- return false;
- }
- try {
- final PackageManager pm = getActivity().getPackageManager();
- return pm.getApplicationEnabledSetting(UIIntents.CMAS_COMPONENT)
- != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
- } catch (final IllegalArgumentException ignored) {
- // CMAS app not installed.
- }
- return false;
- }
-
- private void updateGroupMmsPrefSummary() {
- final boolean groupMmsEnabled = getPreferenceScreen().getSharedPreferences().getBoolean(
- mGroupMmsPrefKey, getResources().getBoolean(R.bool.group_mms_pref_default));
- mGroupMmsPreference.setSummary(groupMmsEnabled ?
- R.string.enable_group_mms : R.string.disable_group_mms);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- getPreferenceScreen().getSharedPreferences()
- .registerOnSharedPreferenceChangeListener(this);
- }
-
- @Override
- public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences,
- final String key) {
- if (key.equals(mGroupMmsPrefKey)) {
- updateGroupMmsPrefSummary();
- } else if (key.equals(mPhoneNumberKey)) {
- // Save the changed phone number in preferences specific to the sub id
- final String newPhoneNumber = mPhoneNumberPreference.getText();
- final BuglePrefs subPrefs = BuglePrefs.getSubscriptionPrefs(mSubId);
- if (TextUtils.isEmpty(newPhoneNumber)) {
- subPrefs.remove(mPhoneNumberKey);
- } else {
- subPrefs.putString(getString(R.string.mms_phone_number_pref_key),
- newPhoneNumber);
- }
- // Update the self participants so the new phone number will be reflected
- // everywhere in the UI.
- ParticipantRefresh.refreshSelfParticipants();
- }
- }
-
- @Override
- public void onPause() {
- super.onPause();
- getPreferenceScreen().getSharedPreferences()
- .unregisterOnSharedPreferenceChangeListener(this);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/appsettings/PhoneNumberPreference.java b/src/com/android/messaging/ui/appsettings/PhoneNumberPreference.java
deleted file mode 100644
index 0c9c018..0000000
--- a/src/com/android/messaging/ui/appsettings/PhoneNumberPreference.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * 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.appsettings;
-
-import android.content.Context;
-import android.preference.EditTextPreference;
-import android.support.v4.text.BidiFormatter;
-import android.support.v4.text.TextDirectionHeuristicsCompat;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-
-import com.android.messaging.R;
-import com.android.messaging.util.PhoneUtils;
-
-/**
- * Preference that displays a phone number and allows editing via a dialog.
- * <p>
- * A default number can be assigned, which is shown in the preference view and
- * used to populate the dialog editor when the preference value is not set. If
- * the user sets the preference to a number equivalent to the default, the
- * underlying preference is cleared.
- */
-public class PhoneNumberPreference extends EditTextPreference {
-
- private String mDefaultPhoneNumber;
- private int mSubId;
-
- public PhoneNumberPreference(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mDefaultPhoneNumber = "";
- }
-
- public void setDefaultPhoneNumber(final String phoneNumber, final int subscriptionId) {
- mDefaultPhoneNumber = phoneNumber;
- mSubId = subscriptionId;
- }
-
- @Override
- protected void onBindView(final View view) {
- // Show the preference value if it's set, or the default number if not.
- // If we don't have a default, fall back to a static string (e.g. Unknown).
- String value = getText();
- if (TextUtils.isEmpty(value)) {
- value = mDefaultPhoneNumber;
- }
- final String displayValue = (!TextUtils.isEmpty(value))
- ? PhoneUtils.get(mSubId).formatForDisplay(value)
- : getContext().getString(R.string.unknown_phone_number_pref_display_value);
- final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
- final String phoneNumber = bidiFormatter.unicodeWrap
- (displayValue, TextDirectionHeuristicsCompat.LTR);
- // Set the value as the summary and let the superclass populate the views
- setSummary(phoneNumber);
- super.onBindView(view);
- }
-
- @Override
- protected void onBindDialogView(final View view) {
- super.onBindDialogView(view);
-
- final String value = getText();
-
- // If the preference is empty, populate the EditText with the default number instead.
- if (TextUtils.isEmpty(value) && !TextUtils.isEmpty(mDefaultPhoneNumber)) {
- final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
- final String phoneNumber = bidiFormatter.unicodeWrap
- (PhoneUtils.get(mSubId).getCanonicalBySystemLocale(mDefaultPhoneNumber),
- TextDirectionHeuristicsCompat.LTR);
- getEditText().setText(phoneNumber);
- }
- getEditText().setInputType(InputType.TYPE_CLASS_PHONE);
- }
-
- @Override
- protected void onDialogClosed(final boolean positiveResult) {
- if (positiveResult && mDefaultPhoneNumber != null) {
- final String value = getEditText().getText().toString();
- final PhoneUtils phoneUtils = PhoneUtils.get(mSubId);
- final String phoneNumber = phoneUtils.getCanonicalBySystemLocale(value);
- final String defaultPhoneNumber = phoneUtils.getCanonicalBySystemLocale(
- mDefaultPhoneNumber);
-
- // If the new value is the default, clear the preference.
- if (phoneNumber.equals(defaultPhoneNumber)) {
- setText("");
- return;
- }
- }
- super.onDialogClosed(positiveResult);
- }
-
- @Override
- public void setText(final String text) {
- super.setText(text);
-
- // EditTextPreference doesn't show the value on the preference view, but we do.
- // We thus need to force a rebind of the view when a new value is set.
- notifyChanged();
- }
-}
diff --git a/src/com/android/messaging/ui/appsettings/SettingsActivity.java b/src/com/android/messaging/ui/appsettings/SettingsActivity.java
deleted file mode 100644
index 75266d8..0000000
--- a/src/com/android/messaging/ui/appsettings/SettingsActivity.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * 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.appsettings;
-
-import android.app.Fragment;
-import android.content.Context;
-import android.os.Bundle;
-import android.support.v4.app.NavUtils;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.SettingsData;
-import com.android.messaging.datamodel.data.SettingsData.SettingsDataListener;
-import com.android.messaging.datamodel.data.SettingsData.SettingsItem;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.PhoneUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Shows the "master" settings activity that contains two parts, one for application-wide settings
- * (dubbed "General settings"), and one or more for per-subscription settings (dubbed "Messaging
- * settings" for single-SIM, and the actual SIM name for multi-SIM). Clicking on either item
- * (e.g. "General settings") will open the detail settings activity (ApplicationSettingsActivity
- * in this case).
- */
-public class SettingsActivity extends BugleActionBarActivity {
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
-
- // Directly open the detailed settings page as the top-level settings activity if this is
- // not a multi-SIM device.
- if (PhoneUtils.getDefault().getActiveSubscriptionCount() <= 1) {
- UIIntents.get().launchApplicationSettingsActivity(this, true /* topLevel */);
- finish();
- } else {
- getFragmentManager().beginTransaction()
- .replace(android.R.id.content, new SettingsFragment())
- .commit();
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- NavUtils.navigateUpFromSameTask(this);
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- public static class SettingsFragment extends Fragment implements SettingsDataListener {
- private ListView mListView;
- private SettingsListAdapter mAdapter;
- private final Binding<SettingsData> mBinding = BindingBase.createBinding(this);
-
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mBinding.bind(DataModel.get().createSettingsData(getActivity(), this));
- mBinding.getData().init(getLoaderManager(), mBinding);
- }
-
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.settings_fragment, container, false);
- mListView = (ListView) view.findViewById(android.R.id.list);
- mAdapter = new SettingsListAdapter(getActivity());
- mListView.setAdapter(mAdapter);
- return view;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mBinding.unbind();
- }
-
- @Override
- public void onSelfParticipantDataLoaded(SettingsData data) {
- mBinding.ensureBound(data);
- mAdapter.setSettingsItems(data.getSettingsItems());
- }
-
- /**
- * An adapter that displays a list of SettingsItem.
- */
- private class SettingsListAdapter extends ArrayAdapter<SettingsItem> {
- public SettingsListAdapter(final Context context) {
- super(context, R.layout.settings_item_view, new ArrayList<SettingsItem>());
- }
-
- public void setSettingsItems(final List<SettingsItem> newList) {
- clear();
- addAll(newList);
- notifyDataSetChanged();
- }
-
- @Override
- public View getView(final int position, final View convertView,
- final ViewGroup parent) {
- View itemView;
- if (convertView != null) {
- itemView = convertView;
- } else {
- final LayoutInflater inflater = (LayoutInflater) getContext()
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- itemView = inflater.inflate(
- R.layout.settings_item_view, parent, false);
- }
- final SettingsItem item = getItem(position);
- final TextView titleTextView = (TextView) itemView.findViewById(R.id.title);
- final TextView subtitleTextView = (TextView) itemView.findViewById(R.id.subtitle);
- final String summaryText = item.getDisplayDetail();
- titleTextView.setText(item.getDisplayName());
- if (!TextUtils.isEmpty(summaryText)) {
- subtitleTextView.setText(summaryText);
- subtitleTextView.setVisibility(View.VISIBLE);
- } else {
- subtitleTextView.setVisibility(View.GONE);
- }
- itemView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- switch (item.getType()) {
- case SettingsItem.TYPE_GENERAL_SETTINGS:
- UIIntents.get().launchApplicationSettingsActivity(getActivity(),
- false /* topLevel */);
- break;
-
- case SettingsItem.TYPE_PER_SUBSCRIPTION_SETTINGS:
- UIIntents.get().launchPerSubscriptionSettingsActivity(getActivity(),
- item.getSubId(), item.getActivityTitle());
- break;
-
- default:
- Assert.fail("unrecognized setting type!");
- break;
- }
- }
- });
- return itemView;
- }
- }
- }
-}
diff --git a/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserActivity.java b/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserActivity.java
deleted file mode 100644
index a540597..0000000
--- a/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserActivity.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.attachmentchooser;
-
-import android.app.Fragment;
-import android.os.Bundle;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.attachmentchooser.AttachmentChooserFragment.AttachmentChooserFragmentHost;
-import com.android.messaging.util.Assert;
-
-public class AttachmentChooserActivity extends BugleActionBarActivity implements
- AttachmentChooserFragmentHost {
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.attachment_chooser_activity);
- getSupportActionBar().setDisplayHomeAsUpEnabled(false);
- }
-
- @Override
- public void onAttachFragment(final Fragment fragment) {
- if (fragment instanceof AttachmentChooserFragment) {
- final String conversationId =
- getIntent().getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
- Assert.notNull(conversationId);
- final AttachmentChooserFragment chooserFragment =
- (AttachmentChooserFragment) fragment;
- chooserFragment.setConversationId(conversationId);
- chooserFragment.setHost(this);
- }
- }
-
- @Override
- public void onConfirmSelection() {
- setResult(RESULT_OK);
- finish();
- }
-}
diff --git a/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragment.java b/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragment.java
deleted file mode 100644
index b39dc3e..0000000
--- a/src/com/android/messaging/ui/attachmentchooser/AttachmentChooserFragment.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * 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.attachmentchooser;
-
-import android.app.Fragment;
-import android.content.Context;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.DraftMessageData;
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.attachmentchooser.AttachmentGridView.AttachmentGridHost;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class AttachmentChooserFragment extends Fragment implements DraftMessageDataListener,
- AttachmentGridHost {
- public interface AttachmentChooserFragmentHost {
- void onConfirmSelection();
- }
-
- private AttachmentGridView mAttachmentGridView;
- private AttachmentGridAdapter mAdapter;
- private AttachmentChooserFragmentHost mHost;
-
- @VisibleForTesting
- Binding<DraftMessageData> mBinding = BindingBase.createBinding(this);
-
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.attachment_chooser_fragment, container, false);
- mAttachmentGridView = (AttachmentGridView) view.findViewById(R.id.grid);
- mAdapter = new AttachmentGridAdapter(getActivity());
- mAttachmentGridView.setAdapter(mAdapter);
- mAttachmentGridView.setHost(this);
- setHasOptionsMenu(true);
- return view;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mBinding.unbind();
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- super.onCreateOptionsMenu(menu, inflater);
- inflater.inflate(R.menu.attachment_chooser_menu, menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.action_confirm_selection:
- confirmSelection();
- return true;
-
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- @VisibleForTesting
- void confirmSelection() {
- if (mBinding.isBound()) {
- mBinding.getData().removeExistingAttachments(
- mAttachmentGridView.getUnselectedAttachments());
- mBinding.getData().saveToStorage(mBinding);
- mHost.onConfirmSelection();
- }
- }
-
- public void setConversationId(final String conversationId) {
- mBinding.bind(DataModel.get().createDraftMessageData(conversationId));
- mBinding.getData().addListener(this);
- mBinding.getData().loadFromStorage(mBinding, null, false);
- }
-
- public void setHost(final AttachmentChooserFragmentHost host) {
- mHost = host;
- }
-
- @Override
- public void onDraftChanged(final DraftMessageData data, final int changeFlags) {
- mBinding.ensureBound(data);
- if ((changeFlags & DraftMessageData.ATTACHMENTS_CHANGED) ==
- DraftMessageData.ATTACHMENTS_CHANGED) {
- mAdapter.onAttachmentsLoaded(data.getReadOnlyAttachments());
- }
- }
-
- @Override
- public void onDraftAttachmentLimitReached(final DraftMessageData data) {
- // Do nothing since the user is in the process of unselecting attachments.
- }
-
- @Override
- public void onDraftAttachmentLoadFailed() {
- // Do nothing since the user is in the process of unselecting attachments.
- }
-
- @Override
- public void displayPhoto(final Rect viewRect, final Uri photoUri) {
- final Uri imagesUri = MessagingContentProvider.buildDraftImagesUri(
- mBinding.getData().getConversationId());
- UIIntents.get().launchFullScreenPhotoViewer(
- getActivity(), photoUri, viewRect, imagesUri);
- }
-
- @Override
- public void updateSelectionCount(int count) {
- if (getActivity() instanceof BugleActionBarActivity) {
- final ActionBar actionBar = ((BugleActionBarActivity) getActivity())
- .getSupportActionBar();
- if (actionBar != null) {
- actionBar.setTitle(getResources().getString(
- R.string.attachment_chooser_selection, count));
- }
- }
- }
-
- class AttachmentGridAdapter extends ArrayAdapter<MessagePartData> {
- public AttachmentGridAdapter(final Context context) {
- super(context, R.layout.attachment_grid_item_view, new ArrayList<MessagePartData>());
- }
-
- public void onAttachmentsLoaded(final List<MessagePartData> attachments) {
- clear();
- addAll(attachments);
- notifyDataSetChanged();
- }
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parent) {
- AttachmentGridItemView itemView;
- final MessagePartData item = getItem(position);
- if (convertView != null && convertView instanceof AttachmentGridItemView) {
- itemView = (AttachmentGridItemView) convertView;
- } else {
- final LayoutInflater inflater = (LayoutInflater) getContext()
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- itemView = (AttachmentGridItemView) inflater.inflate(
- R.layout.attachment_grid_item_view, parent, false);
- }
- itemView.bind(item, mAttachmentGridView);
- return itemView;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/attachmentchooser/AttachmentGridItemView.java b/src/com/android/messaging/ui/attachmentchooser/AttachmentGridItemView.java
deleted file mode 100644
index 8bb7356..0000000
--- a/src/com/android/messaging/ui/attachmentchooser/AttachmentGridItemView.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * 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.attachmentchooser;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.TouchDelegate;
-import android.view.View;
-import android.widget.CheckBox;
-import android.widget.FrameLayout;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.ui.AttachmentPreviewFactory;
-import com.android.messaging.util.Assert;
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * Shows an item in the attachment picker grid.
- */
-public class AttachmentGridItemView extends FrameLayout {
- public interface HostInterface {
- boolean isItemSelected(MessagePartData attachment);
- void onItemCheckedChanged(AttachmentGridItemView view, MessagePartData attachment);
- void onItemClicked(AttachmentGridItemView view, MessagePartData attachment);
- }
-
- @VisibleForTesting
- MessagePartData mAttachmentData;
- private FrameLayout mAttachmentViewContainer;
- private CheckBox mCheckBox;
- private HostInterface mHostInterface;
-
- public AttachmentGridItemView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mAttachmentViewContainer = (FrameLayout) findViewById(R.id.attachment_container);
- mCheckBox = (CheckBox) findViewById(R.id.checkbox);
- mCheckBox.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View v) {
- mHostInterface.onItemCheckedChanged(AttachmentGridItemView.this, mAttachmentData);
- }
- });
- setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View v) {
- mHostInterface.onItemClicked(AttachmentGridItemView.this, mAttachmentData);
- }
- });
- addOnLayoutChangeListener(new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- // Enlarge the clickable region for the checkbox.
- final int touchAreaIncrease = getResources().getDimensionPixelOffset(
- R.dimen.attachment_grid_checkbox_area_increase);
- final Rect region = new Rect();
- mCheckBox.getHitRect(region);
- region.inset(-touchAreaIncrease, -touchAreaIncrease);
- setTouchDelegate(new TouchDelegate(region, mCheckBox));
- }
- });
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- // The grid view auto-fits the columns, so we want to let the height match the width
- // to make the attachment preview square.
- super.onMeasure(widthMeasureSpec, widthMeasureSpec);
- }
-
- public void bind(final MessagePartData attachment, final HostInterface hostInterface) {
- Assert.isTrue(attachment.isAttachment());
- mHostInterface = hostInterface;
- updateSelectedState();
- if (mAttachmentData == null || !mAttachmentData.equals(attachment)) {
- mAttachmentData = attachment;
- updateAttachmentView();
- }
- }
-
- @VisibleForTesting
- HostInterface testGetHostInterface() {
- return mHostInterface;
- }
-
- public void updateSelectedState() {
- mCheckBox.setChecked(mHostInterface.isItemSelected(mAttachmentData));
- }
-
- private void updateAttachmentView() {
- mAttachmentViewContainer.removeAllViews();
- final LayoutInflater inflater = LayoutInflater.from(getContext());
- final View attachmentView = AttachmentPreviewFactory.createAttachmentPreview(inflater,
- mAttachmentData, mAttachmentViewContainer,
- AttachmentPreviewFactory.TYPE_CHOOSER_GRID, true /* startImageRequest */, null);
- mAttachmentViewContainer.addView(attachmentView);
- }
-}
diff --git a/src/com/android/messaging/ui/attachmentchooser/AttachmentGridView.java b/src/com/android/messaging/ui/attachmentchooser/AttachmentGridView.java
deleted file mode 100644
index abf61dc..0000000
--- a/src/com/android/messaging/ui/attachmentchooser/AttachmentGridView.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * 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.attachmentchooser;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.widget.GridView;
-
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.ui.attachmentchooser.AttachmentChooserFragment.AttachmentGridAdapter;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.UiUtils;
-
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Displays a grid of attachment previews for the user to choose which to select/unselect
- */
-public class AttachmentGridView extends GridView implements
- AttachmentGridItemView.HostInterface {
- public interface AttachmentGridHost {
- void displayPhoto(final Rect viewRect, final Uri photoUri);
- void updateSelectionCount(final int count);
- }
-
- // By default everything is selected so only need to keep track of the unselected set.
- private final Set<MessagePartData> mUnselectedSet;
- private AttachmentGridHost mHost;
-
- public AttachmentGridView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mUnselectedSet = new HashSet<>();
- }
-
- public void setHost(final AttachmentGridHost host) {
- mHost = host;
- }
-
- @Override
- public boolean isItemSelected(final MessagePartData attachment) {
- return !mUnselectedSet.contains(attachment);
- }
-
- @Override
- public void onItemClicked(final AttachmentGridItemView view, final MessagePartData attachment) {
- // If the item is an image, show the photo viewer. All the other types (video, audio,
- // vcard) have internal click handling for showing previews so we don't need to handle them
- if (attachment.isImage()) {
- mHost.displayPhoto(UiUtils.getMeasuredBoundsOnScreen(view), attachment.getContentUri());
- }
- }
-
- @Override
- public void onItemCheckedChanged(AttachmentGridItemView view, MessagePartData attachment) {
- // Toggle selection.
- if (isItemSelected(attachment)) {
- mUnselectedSet.add(attachment);
- } else {
- mUnselectedSet.remove(attachment);
- }
- view.updateSelectedState();
- updateSelectionCount();
- }
-
- public Set<MessagePartData> getUnselectedAttachments() {
- return Collections.unmodifiableSet(mUnselectedSet);
- }
-
- private void updateSelectionCount() {
- final int count = getAdapter().getCount() - mUnselectedSet.size();
- Assert.isTrue(count >= 0);
- mHost.updateSelectionCount(count);
- }
-
- private void refreshViews() {
- if (getAdapter() instanceof AttachmentGridAdapter) {
- ((AttachmentGridAdapter) getAdapter()).notifyDataSetChanged();
- }
- }
-
- @Override
- public Parcelable onSaveInstanceState() {
- final Parcelable superState = super.onSaveInstanceState();
- final SavedState savedState = new SavedState(superState);
- savedState.unselectedParts = mUnselectedSet
- .toArray(new MessagePartData[mUnselectedSet.size()]);
- return savedState;
- }
-
- @Override
- public void onRestoreInstanceState(final Parcelable state) {
- if (!(state instanceof SavedState)) {
- super.onRestoreInstanceState(state);
- return;
- }
-
- final SavedState savedState = (SavedState) state;
- super.onRestoreInstanceState(savedState.getSuperState());
- mUnselectedSet.clear();
- for (int i = 0; i < savedState.unselectedParts.length; i++) {
- final MessagePartData unselectedPart = savedState.unselectedParts[i];
- mUnselectedSet.add(unselectedPart);
- }
- refreshViews();
- }
-
- /**
- * Persists the item selection state to saved instance state so we can restore on activity
- * recreation
- */
- public static class SavedState extends BaseSavedState {
- MessagePartData[] unselectedParts;
-
- SavedState(final Parcelable superState) {
- super(superState);
- }
-
- private SavedState(final Parcel in) {
- super(in);
-
- // Read parts
- final int partCount = in.readInt();
- unselectedParts = new MessagePartData[partCount];
- for (int i = 0; i < partCount; i++) {
- unselectedParts[i] = ((MessagePartData) in.readParcelable(
- MessagePartData.class.getClassLoader()));
- }
- }
-
- @Override
- public void writeToParcel(final Parcel out, final int flags) {
- super.writeToParcel(out, flags);
-
- // Write parts
- out.writeInt(unselectedParts.length);
- for (final MessagePartData image : unselectedParts) {
- out.writeParcelable(image, flags);
- }
- }
-
- public static final Parcelable.Creator<SavedState> CREATOR =
- new Parcelable.Creator<SavedState>() {
- @Override
- public SavedState createFromParcel(final Parcel in) {
- return new SavedState(in);
- }
- @Override
- public SavedState[] newArray(final int size) {
- return new SavedState[size];
- }
- };
- }
-}
diff --git a/src/com/android/messaging/ui/contact/AddContactsConfirmationDialog.java b/src/com/android/messaging/ui/contact/AddContactsConfirmationDialog.java
deleted file mode 100644
index 9c1393d..0000000
--- a/src/com/android/messaging/ui/contact/AddContactsConfirmationDialog.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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.contact;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.Button;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.ContactIconView;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.AccessibilityUtil;
-
-public class AddContactsConfirmationDialog implements DialogInterface.OnClickListener {
- private final Context mContext;
- private final Uri mAvatarUri;
- private final String mNormalizedDestination;
-
- public AddContactsConfirmationDialog(final Context context, final Uri avatarUri,
- final String normalizedDestination) {
- mContext = context;
- mAvatarUri = avatarUri;
- mNormalizedDestination = normalizedDestination;
- }
-
- public void show() {
- final int confirmAddContactStringId = R.string.add_contact_confirmation;
- final int cancelStringId = android.R.string.cancel;
- final AlertDialog alertDialog = new AlertDialog.Builder(mContext)
- .setTitle(R.string.add_contact_confirmation_dialog_title)
- .setView(createBodyView())
- .setPositiveButton(confirmAddContactStringId, this)
- .setNegativeButton(cancelStringId, null)
- .create();
- alertDialog.show();
- final Resources resources = mContext.getResources();
- final Button cancelButton = alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE);
- if (cancelButton != null) {
- cancelButton.setTextColor(resources.getColor(R.color.contact_picker_button_text_color));
- }
- final Button addButton = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE);
- if (addButton != null) {
- addButton.setTextColor(resources.getColor(R.color.contact_picker_button_text_color));
- }
- }
-
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- UIIntents.get().launchAddContactActivity(mContext, mNormalizedDestination);
- }
-
- private View createBodyView() {
- final View view = LayoutInflater.from(mContext).inflate(
- R.layout.add_contacts_confirmation_dialog_body, null);
- final ContactIconView iconView = (ContactIconView) view.findViewById(R.id.contact_icon);
- iconView.setImageResourceUri(mAvatarUri);
- final TextView textView = (TextView) view.findViewById(R.id.participant_name);
- textView.setText(mNormalizedDestination);
- // Accessibility reason : in case phone numbers are mixed in the display name,
- // we need to vocalize it for talkback.
- final String vocalizedDisplayName = AccessibilityUtil.getVocalizedPhoneNumber(
- mContext.getResources(), mNormalizedDestination);
- textView.setContentDescription(vocalizedDisplayName);
- return view;
- }
-}
diff --git a/src/com/android/messaging/ui/contact/AllContactsListViewHolder.java b/src/com/android/messaging/ui/contact/AllContactsListViewHolder.java
deleted file mode 100644
index 7263c54..0000000
--- a/src/com/android/messaging/ui/contact/AllContactsListViewHolder.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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.contact;
-
-import android.content.Context;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.CustomHeaderPagerListViewHolder;
-import com.android.messaging.ui.contact.ContactListItemView.HostInterface;
-
-/**
- * Holds the all contacts view for the contact picker's view pager.
- */
-public class AllContactsListViewHolder extends CustomHeaderPagerListViewHolder {
- public AllContactsListViewHolder(final Context context, final HostInterface clivHostInterface) {
- super(context, new ContactListAdapter(context, null, clivHostInterface,
- true /* needAlphabetHeader */));
- }
-
- @Override
- protected int getLayoutResId() {
- return R.layout.all_contacts_list_view;
- }
-
- @Override
- protected int getPageTitleResId() {
- return R.string.contact_picker_all_contacts_tab_title;
- }
-
- @Override
- protected int getEmptyViewResId() {
- return R.id.empty_view;
- }
-
- @Override
- protected int getListViewResId() {
- return R.id.all_contacts_list;
- }
-
- @Override
- protected int getEmptyViewTitleResId() {
- return R.string.contact_list_empty_text;
- }
-
- @Override
- protected int getEmptyViewImageResId() {
- return R.drawable.ic_oobe_freq_list;
- }
-}
diff --git a/src/com/android/messaging/ui/contact/ContactDropdownLayouter.java b/src/com/android/messaging/ui/contact/ContactDropdownLayouter.java
deleted file mode 100644
index 7df62de..0000000
--- a/src/com/android/messaging/ui/contact/ContactDropdownLayouter.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * 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.contact;
-
-import android.content.Context;
-import android.graphics.drawable.StateListDrawable;
-import android.net.Uri;
-import android.support.v4.text.BidiFormatter;
-import android.support.v4.text.TextDirectionHeuristicsCompat;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-
-import com.android.ex.chips.DropdownChipLayouter;
-import com.android.ex.chips.RecipientEntry;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ContactListItemData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.ui.ContactIconView;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.ContactRecipientEntryUtils;
-
-/**
- * An implementation for {@link DropdownChipLayouter}. Layouts the dropdown
- * list in the ContactRecipientAutoCompleteView in Material style.
- */
-public class ContactDropdownLayouter extends DropdownChipLayouter {
- private final ContactListItemView.HostInterface mClivHostInterface;
-
- public ContactDropdownLayouter(final LayoutInflater inflater, final Context context,
- final ContactListItemView.HostInterface clivHostInterface) {
- super(inflater, context);
- mClivHostInterface = new ContactListItemView.HostInterface() {
-
- @Override
- public void onContactListItemClicked(final ContactListItemData item,
- final ContactListItemView view) {
- // The chips UI will handle auto-complete item click events, so No-op here.
- }
-
- @Override
- public boolean isContactSelected(final ContactListItemData item) {
- // In chips drop down we don't show any selected checkmark per design.
- return false;
- }
- };
- }
-
- /**
- * Bind a drop down view to a RecipientEntry. We'd like regular dropdown items (BASE_RECIPIENT)
- * to behave the same as regular ContactListItemViews, while using the chips library's
- * item styling for alternates dropdown items (happens when you click on a chip).
- */
- @Override
- public View bindView(final View convertView, final ViewGroup parent, final RecipientEntry entry,
- final int position, AdapterType type, final String substring,
- final StateListDrawable deleteDrawable) {
- if (type != AdapterType.BASE_RECIPIENT) {
- if (type == AdapterType.SINGLE_RECIPIENT) {
- // Treat single recipients the same way as alternates. The base implementation of
- // single recipients would try to simplify the destination by tokenizing. We'd
- // like to always show the full destination address per design request.
- type = AdapterType.RECIPIENT_ALTERNATES;
- }
- return super.bindView(convertView, parent, entry, position, type, substring,
- deleteDrawable);
- }
-
- // Default to show all the information
- // RTL : To format contact name and detail if they happen to be phone numbers.
- final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
- final String displayName = bidiFormatter.unicodeWrap(
- ContactRecipientEntryUtils.getDisplayNameForContactList(entry),
- TextDirectionHeuristicsCompat.LTR);
- final String destination = bidiFormatter.unicodeWrap(
- ContactRecipientEntryUtils.formatDestination(entry),
- TextDirectionHeuristicsCompat.LTR);
- final View itemView = reuseOrInflateView(convertView, parent, type);
-
- // Bold the string that is matched.
- final CharSequence[] styledResults =
- getStyledResults(substring, displayName, destination);
-
- Assert.isTrue(itemView instanceof ContactListItemView);
- final ContactListItemView contactListItemView = (ContactListItemView) itemView;
- contactListItemView.setImageClickHandlerDisabled(true);
- contactListItemView.bind(entry, styledResults[0], styledResults[1],
- mClivHostInterface, (type == AdapterType.SINGLE_RECIPIENT));
- return itemView;
- }
-
- @Override
- protected void bindIconToView(boolean showImage, RecipientEntry entry, ImageView view,
- AdapterType type) {
- if (showImage && view instanceof ContactIconView) {
- final ContactIconView contactView = (ContactIconView) view;
- // These show contact cards by default, but that isn't what we want here
- contactView.setImageClickHandlerDisabled(true);
- final Uri avatarUri = AvatarUriUtil.createAvatarUri(
- ParticipantData.getFromRecipientEntry(entry));
- contactView.setImageResourceUri(avatarUri);
- } else {
- super.bindIconToView(showImage, entry, view, type);
- }
- }
-
- @Override
- protected int getItemLayoutResId(AdapterType type) {
- switch (type) {
- case BASE_RECIPIENT:
- return R.layout.contact_list_item_view;
- case RECIPIENT_ALTERNATES:
- return R.layout.chips_alternates_dropdown_item;
- default:
- return R.layout.chips_alternates_dropdown_item;
- }
- }
-
- @Override
- protected int getAlternateItemLayoutResId(AdapterType type) {
- return getItemLayoutResId(type);
- }
-}
diff --git a/src/com/android/messaging/ui/contact/ContactListAdapter.java b/src/com/android/messaging/ui/contact/ContactListAdapter.java
deleted file mode 100644
index d466b61..0000000
--- a/src/com/android/messaging/ui/contact/ContactListAdapter.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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.contact;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CursorAdapter;
-import android.widget.SectionIndexer;
-
-import com.android.messaging.R;
-import com.android.messaging.util.Assert;
-
-public class ContactListAdapter extends CursorAdapter implements SectionIndexer {
- private final ContactListItemView.HostInterface mClivHostInterface;
- private final boolean mNeedAlphabetHeader;
- private ContactSectionIndexer mSectionIndexer;
-
- public ContactListAdapter(final Context context, final Cursor cursor,
- final ContactListItemView.HostInterface clivHostInterface,
- final boolean needAlphabetHeader) {
- super(context, cursor, 0);
- mClivHostInterface = clivHostInterface;
- mNeedAlphabetHeader = needAlphabetHeader;
- mSectionIndexer = new ContactSectionIndexer(cursor);
- }
-
- @Override
- public void bindView(final View view, final Context context, final Cursor cursor) {
- Assert.isTrue(view instanceof ContactListItemView);
- final ContactListItemView contactListItemView = (ContactListItemView) view;
- String alphabetHeader = null;
- if (mNeedAlphabetHeader) {
- final int position = cursor.getPosition();
- final int section = mSectionIndexer.getSectionForPosition(position);
- // Check if the position is the first in the section.
- if (mSectionIndexer.getPositionForSection(section) == position) {
- alphabetHeader = (String) mSectionIndexer.getSections()[section];
- }
- }
- contactListItemView.bind(cursor, mClivHostInterface, mNeedAlphabetHeader, alphabetHeader);
- }
-
- @Override
- public View newView(final Context context, final Cursor cursor, final ViewGroup parent) {
- final LayoutInflater layoutInflater = LayoutInflater.from(context);
- return layoutInflater.inflate(R.layout.contact_list_item_view, parent, false);
- }
-
- @Override
- public Cursor swapCursor(final Cursor newCursor) {
- mSectionIndexer = new ContactSectionIndexer(newCursor);
- return super.swapCursor(newCursor);
- }
-
- @Override
- public Object[] getSections() {
- return mSectionIndexer.getSections();
- }
-
- @Override
- public int getPositionForSection(final int sectionIndex) {
- return mSectionIndexer.getPositionForSection(sectionIndex);
- }
-
- @Override
- public int getSectionForPosition(final int position) {
- return mSectionIndexer.getSectionForPosition(position);
- }
-}
diff --git a/src/com/android/messaging/ui/contact/ContactListItemView.java b/src/com/android/messaging/ui/contact/ContactListItemView.java
deleted file mode 100644
index 6904da6..0000000
--- a/src/com/android/messaging/ui/contact/ContactListItemView.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * 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.contact;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.ex.chips.RecipientEntry;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.data.ContactListItemData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.ui.ContactIconView;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * The view for a single entry in a contact list.
- */
-public class ContactListItemView extends LinearLayout implements OnClickListener {
- public interface HostInterface {
- void onContactListItemClicked(ContactListItemData item, ContactListItemView view);
- boolean isContactSelected(ContactListItemData item);
- }
-
- @VisibleForTesting
- final ContactListItemData mData;
- private TextView mContactNameTextView;
- private TextView mContactDetailsTextView;
- private TextView mContactDetailTypeTextView;
- private TextView mAlphabetHeaderTextView;
- private ContactIconView mContactIconView;
- private ImageView mContactCheckmarkView;
- private HostInterface mHostInterface;
- private boolean mShouldShowAlphabetHeader;
-
- public ContactListItemView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mData = DataModel.get().createContactListItemData();
- }
-
- @Override
- protected void onFinishInflate () {
- mContactNameTextView = (TextView) findViewById(R.id.contact_name);
- mContactDetailsTextView = (TextView) findViewById(R.id.contact_details);
- mContactDetailTypeTextView = (TextView) findViewById(R.id.contact_detail_type);
- mAlphabetHeaderTextView = (TextView) findViewById(R.id.alphabet_header);
- mContactIconView = (ContactIconView) findViewById(R.id.contact_icon);
- mContactCheckmarkView = (ImageView) findViewById(R.id.contact_checkmark);
- }
-
- /**
- * Fills in the data associated with this view by binding to a contact cursor provided by
- * ContactUtil.
- * @param cursor the contact cursor.
- * @param hostInterface host interface to this view.
- * @param shouldShowAlphabetHeader whether an alphabetical header should shown on the side
- * of this view. If {@code headerLabel} is empty, we will still leave space for it.
- * @param headerLabel the alphabetical header on the side of this view, if it should be shown.
- */
- public void bind(final Cursor cursor, final HostInterface hostInterface,
- final boolean shouldShowAlphabetHeader, final String headerLabel) {
- mData.bind(cursor, headerLabel);
- mHostInterface = hostInterface;
- mShouldShowAlphabetHeader = shouldShowAlphabetHeader;
- setOnClickListener(this);
- updateViewAppearance();
- }
-
- /**
- * Binds a RecipientEntry. This is used by the chips text view's dropdown layout.
- * @param recipientEntry the source RecipientEntry provided by ContactDropdownLayouter, which
- * was in turn directly from one of the existing chips, or from filtered results
- * generated by ContactRecipientAdapter.
- * @param styledName display name where the portion that matches the search text is bold.
- * @param styledDestination number where the portion that matches the search text is bold.
- * @param hostInterface host interface to this view.
- * @param isSingleRecipient whether this item is shown as the only line item in the single
- * recipient drop down from the chips view. If this is the case, we always show the
- * contact avatar even if it's not a first-level entry.
- */
- public void bind(final RecipientEntry recipientEntry, final CharSequence styledName,
- final CharSequence styledDestination, final HostInterface hostInterface,
- final boolean isSingleRecipient) {
- mData.bind(recipientEntry, styledName, styledDestination, isSingleRecipient);
- mHostInterface = hostInterface;
- mShouldShowAlphabetHeader = false;
- updateViewAppearance();
- }
-
- private void updateViewAppearance() {
- mContactNameTextView.setText(mData.getDisplayName());
- mContactDetailsTextView.setText(mData.getDestination());
- mContactDetailTypeTextView.setText(Phone.getTypeLabel(getResources(),
- mData.getDestinationType(), mData.getDestinationLabel()));
- final RecipientEntry recipientEntry = mData.getRecipientEntry();
- final String destinationString = String.valueOf(mData.getDestination());
- if (mData.getIsSimpleContactItem()) {
- // This is a special number-with-avatar type of contact (for unknown contact chips
- // and for direct "send to destination" item). In this case, make sure we only show
- // the display name (phone number) and the avatar and hide everything else.
- final Uri avatarUri = AvatarUriUtil.createAvatarUri(
- ParticipantData.getFromRecipientEntry(recipientEntry));
- mContactIconView.setImageResourceUri(avatarUri, mData.getContactId(),
- mData.getLookupKey(), destinationString);
- mContactIconView.setVisibility(VISIBLE);
- mContactCheckmarkView.setVisibility(GONE);
- mContactDetailTypeTextView.setVisibility(GONE);
- mContactDetailsTextView.setVisibility(GONE);
- mContactNameTextView.setVisibility(VISIBLE);
- } else if (mData.getIsFirstLevel()) {
- final Uri avatarUri = AvatarUriUtil.createAvatarUri(
- ParticipantData.getFromRecipientEntry(recipientEntry));
- mContactIconView.setImageResourceUri(avatarUri, mData.getContactId(),
- mData.getLookupKey(), destinationString);
- mContactIconView.setVisibility(VISIBLE);
- mContactNameTextView.setVisibility(VISIBLE);
- final boolean isSelected = mHostInterface.isContactSelected(mData);
- setSelected(isSelected);
- mContactCheckmarkView.setVisibility(isSelected ? VISIBLE : GONE);
- mContactDetailsTextView.setVisibility(VISIBLE);
- mContactDetailTypeTextView.setVisibility(VISIBLE);
- } else {
- mContactIconView.setImageResourceUri(null);
- mContactIconView.setVisibility(INVISIBLE);
- mContactNameTextView.setVisibility(GONE);
- final boolean isSelected = mHostInterface.isContactSelected(mData);
- setSelected(isSelected);
- mContactCheckmarkView.setVisibility(isSelected ? VISIBLE : GONE);
- mContactDetailsTextView.setVisibility(VISIBLE);
- mContactDetailTypeTextView.setVisibility(VISIBLE);
- }
-
- if (mShouldShowAlphabetHeader) {
- mAlphabetHeaderTextView.setVisibility(VISIBLE);
- mAlphabetHeaderTextView.setText(mData.getAlphabetHeader());
- } else {
- mAlphabetHeaderTextView.setVisibility(GONE);
- }
- }
-
- /**
- * {@inheritDoc} from OnClickListener
- */
- @Override
- public void onClick(final View v) {
- Assert.isTrue(v == this);
- Assert.isTrue(mHostInterface != null);
- mHostInterface.onContactListItemClicked(mData, this);
- }
-
- public void setImageClickHandlerDisabled(final boolean isHandlerDisabled) {
- mContactIconView.setImageClickHandlerDisabled(isHandlerDisabled);
- }
-}
diff --git a/src/com/android/messaging/ui/contact/ContactPickerFragment.java b/src/com/android/messaging/ui/contact/ContactPickerFragment.java
deleted file mode 100644
index d803087..0000000
--- a/src/com/android/messaging/ui/contact/ContactPickerFragment.java
+++ /dev/null
@@ -1,607 +0,0 @@
-/*
- * 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.contact;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.database.Cursor;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.support.v7.widget.Toolbar;
-import android.support.v7.widget.Toolbar.OnMenuItemClickListener;
-import android.text.Editable;
-import android.text.InputType;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.transition.Explode;
-import android.transition.Transition;
-import android.transition.Transition.EpicenterCallback;
-import android.transition.TransitionManager;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.action.ActionMonitor;
-import com.android.messaging.datamodel.action.GetOrCreateConversationAction;
-import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionListener;
-import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionMonitor;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.ContactListItemData;
-import com.android.messaging.datamodel.data.ContactPickerData;
-import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.ui.CustomHeaderPagerViewHolder;
-import com.android.messaging.ui.CustomHeaderViewPager;
-import com.android.messaging.ui.animation.ViewGroupItemVerticalExplodeAnimation;
-import com.android.messaging.ui.contact.ContactRecipientAutoCompleteView.ContactChipsChangeListener;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.RunsOnMainThread;
-import com.android.messaging.util.ContactUtil;
-import com.android.messaging.util.ImeUtil;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.UiUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.ArrayList;
-import java.util.Set;
-
-
-/**
- * Shows lists of contacts to start conversations with.
- */
-public class ContactPickerFragment extends Fragment implements ContactPickerDataListener,
- ContactListItemView.HostInterface, ContactChipsChangeListener, OnMenuItemClickListener,
- GetOrCreateConversationActionListener {
- public static final String FRAGMENT_TAG = "contactpicker";
-
- // Undefined contact picker mode. We should never be in this state after the host activity has
- // been created.
- public static final int MODE_UNDEFINED = 0;
-
- // The initial contact picker mode for starting a new conversation with one contact.
- public static final int MODE_PICK_INITIAL_CONTACT = 1;
-
- // The contact picker mode where one initial contact has been picked and we are showing
- // only the chips edit box.
- public static final int MODE_CHIPS_ONLY = 2;
-
- // The contact picker mode for picking more contacts after starting the initial 1-1.
- public static final int MODE_PICK_MORE_CONTACTS = 3;
-
- // The contact picker mode when max number of participants is reached.
- public static final int MODE_PICK_MAX_PARTICIPANTS = 4;
-
- public interface ContactPickerFragmentHost {
- void onGetOrCreateNewConversation(String conversationId);
- void onBackButtonPressed();
- void onInitiateAddMoreParticipants();
- void onParticipantCountChanged(boolean canAddMoreParticipants);
- void invalidateActionBar();
- }
-
- @VisibleForTesting
- final Binding<ContactPickerData> mBinding = BindingBase.createBinding(this);
-
- private ContactPickerFragmentHost mHost;
- private ContactRecipientAutoCompleteView mRecipientTextView;
- private CustomHeaderViewPager mCustomHeaderViewPager;
- private AllContactsListViewHolder mAllContactsListViewHolder;
- private FrequentContactsListViewHolder mFrequentContactsListViewHolder;
- private View mRootView;
- private View mPendingExplodeView;
- private View mComposeDivider;
- private Toolbar mToolbar;
- private int mContactPickingMode = MODE_UNDEFINED;
-
- // Keeps track of the currently selected phone numbers in the chips view to enable fast lookup.
- private Set<String> mSelectedPhoneNumbers = null;
-
- /**
- * {@inheritDoc} from Fragment
- */
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mAllContactsListViewHolder = new AllContactsListViewHolder(getActivity(), this);
- mFrequentContactsListViewHolder = new FrequentContactsListViewHolder(getActivity(), this);
-
- if (ContactUtil.hasReadContactsPermission()) {
- mBinding.bind(DataModel.get().createContactPickerData(getActivity(), this));
- mBinding.getData().init(getLoaderManager(), mBinding);
- }
- }
-
- /**
- * {@inheritDoc} from Fragment
- */
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.contact_picker_fragment, container, false);
- mRecipientTextView = (ContactRecipientAutoCompleteView)
- view.findViewById(R.id.recipient_text_view);
- mRecipientTextView.setThreshold(0);
- mRecipientTextView.setDropDownAnchor(R.id.compose_contact_divider);
-
- mRecipientTextView.setContactChipsListener(this);
- mRecipientTextView.setDropdownChipLayouter(new ContactDropdownLayouter(inflater,
- getActivity(), this));
- mRecipientTextView.setAdapter(new ContactRecipientAdapter(getActivity(), this));
- mRecipientTextView.addTextChangedListener(new TextWatcher() {
- @Override
- public void onTextChanged(final CharSequence s, final int start, final int before,
- final int count) {
- }
-
- @Override
- public void beforeTextChanged(final CharSequence s, final int start, final int count,
- final int after) {
- }
-
- @Override
- public void afterTextChanged(final Editable s) {
- updateTextInputButtonsVisibility();
- }
- });
-
- final CustomHeaderPagerViewHolder[] viewHolders = {
- mFrequentContactsListViewHolder,
- mAllContactsListViewHolder };
-
- mCustomHeaderViewPager = (CustomHeaderViewPager) view.findViewById(R.id.contact_pager);
- mCustomHeaderViewPager.setViewHolders(viewHolders);
- mCustomHeaderViewPager.setViewPagerTabHeight(CustomHeaderViewPager.DEFAULT_TAB_STRIP_SIZE);
- mCustomHeaderViewPager.setBackgroundColor(getResources()
- .getColor(R.color.contact_picker_background));
-
- // The view pager defaults to the frequent contacts page.
- mCustomHeaderViewPager.setCurrentItem(0);
-
- mToolbar = (Toolbar) view.findViewById(R.id.toolbar);
- mToolbar.setNavigationIcon(R.drawable.ic_arrow_back_light);
- mToolbar.setNavigationContentDescription(R.string.back);
- mToolbar.setNavigationOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View v) {
- mHost.onBackButtonPressed();
- }
- });
-
- mToolbar.inflateMenu(R.menu.compose_menu);
- mToolbar.setOnMenuItemClickListener(this);
-
- mComposeDivider = view.findViewById(R.id.compose_contact_divider);
- mRootView = view;
- return view;
- }
-
- /**
- * {@inheritDoc}
- *
- * Called when the host activity has been created. At this point, the host activity should
- * have set the contact picking mode for us so that we may update our visuals.
- */
- @Override
- public void onActivityCreated(final Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- Assert.isTrue(mContactPickingMode != MODE_UNDEFINED);
- updateVisualsForContactPickingMode(false /* animate */);
- mHost.invalidateActionBar();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- // We could not have bound to the data if the permission was denied.
- if (mBinding.isBound()) {
- mBinding.unbind();
- }
-
- if (mMonitor != null) {
- mMonitor.unregister();
- }
- mMonitor = null;
- }
-
- @Override
- public boolean onMenuItemClick(final MenuItem menuItem) {
- switch (menuItem.getItemId()) {
- case R.id.action_ime_dialpad_toggle:
- final int baseInputType = InputType.TYPE_TEXT_FLAG_MULTI_LINE;
- if ((mRecipientTextView.getInputType() & InputType.TYPE_CLASS_PHONE) !=
- InputType.TYPE_CLASS_PHONE) {
- mRecipientTextView.setInputType(baseInputType | InputType.TYPE_CLASS_PHONE);
- menuItem.setIcon(R.drawable.ic_ime_light);
- } else {
- mRecipientTextView.setInputType(baseInputType | InputType.TYPE_CLASS_TEXT);
- menuItem.setIcon(R.drawable.ic_numeric_dialpad);
- }
- ImeUtil.get().showImeKeyboard(getActivity(), mRecipientTextView);
- return true;
-
- case R.id.action_add_more_participants:
- mHost.onInitiateAddMoreParticipants();
- return true;
-
- case R.id.action_confirm_participants:
- maybeGetOrCreateConversation();
- return true;
-
- case R.id.action_delete_text:
- Assert.equals(MODE_PICK_INITIAL_CONTACT, mContactPickingMode);
- mRecipientTextView.setText("");
- return true;
- }
- return false;
- }
-
- @Override // From ContactPickerDataListener
- public void onAllContactsCursorUpdated(final Cursor data) {
- mBinding.ensureBound();
- mAllContactsListViewHolder.onContactsCursorUpdated(data);
- }
-
- @Override // From ContactPickerDataListener
- public void onFrequentContactsCursorUpdated(final Cursor data) {
- mBinding.ensureBound();
- mFrequentContactsListViewHolder.onContactsCursorUpdated(data);
- if (data != null && data.getCount() == 0) {
- // Show the all contacts list when there's no frequents.
- mCustomHeaderViewPager.setCurrentItem(1);
- }
- }
-
- @Override // From ContactListItemView.HostInterface
- public void onContactListItemClicked(final ContactListItemData item,
- final ContactListItemView view) {
- if (!isContactSelected(item)) {
- if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT) {
- mPendingExplodeView = view;
- }
- mRecipientTextView.appendRecipientEntry(item.getRecipientEntry());
- } else if (mContactPickingMode != MODE_PICK_INITIAL_CONTACT) {
- mRecipientTextView.removeRecipientEntry(item.getRecipientEntry());
- }
- }
-
- @Override // From ContactListItemView.HostInterface
- public boolean isContactSelected(final ContactListItemData item) {
- return mSelectedPhoneNumbers != null &&
- mSelectedPhoneNumbers.contains(PhoneUtils.getDefault().getCanonicalBySystemLocale(
- item.getRecipientEntry().getDestination()));
- }
-
- /**
- * Call this immediately after attaching the fragment, or when there's a ui state change that
- * changes our host (i.e. restore from saved instance state).
- */
- public void setHost(final ContactPickerFragmentHost host) {
- mHost = host;
- }
-
- public void setContactPickingMode(final int mode, final boolean animate) {
- if (mContactPickingMode != mode) {
- // Guard against impossible transitions.
- Assert.isTrue(
- // We may start from undefined mode to any mode when we are restoring state.
- (mContactPickingMode == MODE_UNDEFINED) ||
- (mContactPickingMode == MODE_PICK_INITIAL_CONTACT && mode == MODE_CHIPS_ONLY) ||
- (mContactPickingMode == MODE_CHIPS_ONLY && mode == MODE_PICK_MORE_CONTACTS) ||
- (mContactPickingMode == MODE_PICK_MORE_CONTACTS
- && mode == MODE_PICK_MAX_PARTICIPANTS) ||
- (mContactPickingMode == MODE_PICK_MAX_PARTICIPANTS
- && mode == MODE_PICK_MORE_CONTACTS));
-
- mContactPickingMode = mode;
- updateVisualsForContactPickingMode(animate);
- }
- }
-
- private void showImeKeyboard() {
- Assert.notNull(mRecipientTextView);
- mRecipientTextView.requestFocus();
-
- // showImeKeyboard() won't work until the layout is ready, so wait until layout is complete
- // before showing the soft keyboard.
- UiUtils.doOnceAfterLayoutChange(mRootView, new Runnable() {
- @Override
- public void run() {
- final Activity activity = getActivity();
- if (activity != null) {
- ImeUtil.get().showImeKeyboard(activity, mRecipientTextView);
- }
- }
- });
- mRecipientTextView.invalidate();
- }
-
- private void updateVisualsForContactPickingMode(final boolean animate) {
- // Don't update visuals if the visuals haven't been inflated yet.
- if (mRootView != null) {
- final Menu menu = mToolbar.getMenu();
- final MenuItem addMoreParticipantsItem = menu.findItem(
- R.id.action_add_more_participants);
- final MenuItem confirmParticipantsItem = menu.findItem(
- R.id.action_confirm_participants);
- switch (mContactPickingMode) {
- case MODE_PICK_INITIAL_CONTACT:
- addMoreParticipantsItem.setVisible(false);
- confirmParticipantsItem.setVisible(false);
- mCustomHeaderViewPager.setVisibility(View.VISIBLE);
- mComposeDivider.setVisibility(View.INVISIBLE);
- mRecipientTextView.setEnabled(true);
- showImeKeyboard();
- break;
-
- case MODE_CHIPS_ONLY:
- if (animate) {
- if (mPendingExplodeView == null) {
- // The user didn't click on any contact item, so use the toolbar as
- // the view to "explode."
- mPendingExplodeView = mToolbar;
- }
- startExplodeTransitionForContactLists(false /* show */);
-
- ViewGroupItemVerticalExplodeAnimation.startAnimationForView(
- mCustomHeaderViewPager, mPendingExplodeView, mRootView,
- true /* snapshotView */, UiUtils.COMPOSE_TRANSITION_DURATION);
- showHideContactPagerWithAnimation(false /* show */);
- } else {
- mCustomHeaderViewPager.setVisibility(View.GONE);
- }
-
- addMoreParticipantsItem.setVisible(true);
- confirmParticipantsItem.setVisible(false);
- mComposeDivider.setVisibility(View.VISIBLE);
- mRecipientTextView.setEnabled(true);
- break;
-
- case MODE_PICK_MORE_CONTACTS:
- if (animate) {
- // Correctly set the start visibility state for the view pager and
- // individual list items (hidden initially), so that the transition
- // manager can properly track the visibility change for the explode.
- mCustomHeaderViewPager.setVisibility(View.VISIBLE);
- toggleContactListItemsVisibilityForPendingTransition(false /* show */);
- startExplodeTransitionForContactLists(true /* show */);
- }
- addMoreParticipantsItem.setVisible(false);
- confirmParticipantsItem.setVisible(true);
- mCustomHeaderViewPager.setVisibility(View.VISIBLE);
- mComposeDivider.setVisibility(View.INVISIBLE);
- mRecipientTextView.setEnabled(true);
- showImeKeyboard();
- break;
-
- case MODE_PICK_MAX_PARTICIPANTS:
- addMoreParticipantsItem.setVisible(false);
- confirmParticipantsItem.setVisible(true);
- mCustomHeaderViewPager.setVisibility(View.VISIBLE);
- mComposeDivider.setVisibility(View.INVISIBLE);
- // TODO: Verify that this is okay for accessibility
- mRecipientTextView.setEnabled(false);
- break;
-
- default:
- Assert.fail("Unsupported contact picker mode!");
- break;
- }
- updateTextInputButtonsVisibility();
- }
- }
-
- private void updateTextInputButtonsVisibility() {
- final Menu menu = mToolbar.getMenu();
- final MenuItem keypadToggleItem = menu.findItem(R.id.action_ime_dialpad_toggle);
- final MenuItem deleteTextItem = menu.findItem(R.id.action_delete_text);
- if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT) {
- if (TextUtils.isEmpty(mRecipientTextView.getText())) {
- deleteTextItem.setVisible(false);
- keypadToggleItem.setVisible(true);
- } else {
- deleteTextItem.setVisible(true);
- keypadToggleItem.setVisible(false);
- }
- } else {
- deleteTextItem.setVisible(false);
- keypadToggleItem.setVisible(false);
- }
- }
-
- private void maybeGetOrCreateConversation() {
- final ArrayList<ParticipantData> participants =
- mRecipientTextView.getRecipientParticipantDataForConversationCreation();
- if (ContactPickerData.isTooManyParticipants(participants.size())) {
- UiUtils.showToast(R.string.too_many_participants);
- } else if (participants.size() > 0 && mMonitor == null) {
- mMonitor = GetOrCreateConversationAction.getOrCreateConversation(participants,
- null, this);
- }
- }
-
- /**
- * Watches changes in contact chips to determine possible state transitions (e.g. creating
- * the initial conversation, adding more participants or finish the current conversation)
- */
- @Override
- public void onContactChipsChanged(final int oldCount, final int newCount) {
- Assert.isTrue(oldCount != newCount);
- if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT) {
- // Initial picking mode. Start a conversation once a recipient has been picked.
- maybeGetOrCreateConversation();
- } else if (mContactPickingMode == MODE_CHIPS_ONLY) {
- // oldCount == 0 means we are restoring from savedInstanceState to add the existing
- // chips, don't switch to "add more participants" mode in this case.
- if (oldCount > 0 && mRecipientTextView.isFocused()) {
- // Chips only mode. The user may have picked an additional contact or deleted the
- // only existing contact. Either way, switch to picking more participants mode.
- mHost.onInitiateAddMoreParticipants();
- }
- }
- mHost.onParticipantCountChanged(ContactPickerData.getCanAddMoreParticipants(newCount));
-
- // Refresh our local copy of the selected chips set to keep it up-to-date.
- mSelectedPhoneNumbers = mRecipientTextView.getSelectedDestinations();
- invalidateContactLists();
- }
-
- /**
- * Listens for notification that invalid contacts have been removed during resolving them.
- * These contacts were not local contacts, valid email, or valid phone numbers
- */
- @Override
- public void onInvalidContactChipsPruned(final int prunedCount) {
- Assert.isTrue(prunedCount > 0);
- UiUtils.showToast(R.plurals.add_invalid_contact_error, prunedCount);
- }
-
- /**
- * Listens for notification that the user has pressed enter/done on the keyboard with all
- * contacts in place and we should create or go to the existing conversation now
- */
- @Override
- public void onEntryComplete() {
- if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT ||
- mContactPickingMode == MODE_PICK_MORE_CONTACTS ||
- mContactPickingMode == MODE_PICK_MAX_PARTICIPANTS) {
- // Avoid multiple calls to create in race cases (hit done right after selecting contact)
- maybeGetOrCreateConversation();
- }
- }
-
- private void invalidateContactLists() {
- mAllContactsListViewHolder.invalidateList();
- mFrequentContactsListViewHolder.invalidateList();
- }
-
- /**
- * Kicks off a scene transition that animates visibility changes of individual contact list
- * items via explode animation.
- * @param show whether the contact lists are to be shown or hidden.
- */
- private void startExplodeTransitionForContactLists(final boolean show) {
- if (!OsUtil.isAtLeastL()) {
- // Explode animation is not supported pre-L.
- return;
- }
- final Explode transition = new Explode();
- final Rect epicenter = mPendingExplodeView == null ? null :
- UiUtils.getMeasuredBoundsOnScreen(mPendingExplodeView);
- transition.setDuration(UiUtils.COMPOSE_TRANSITION_DURATION);
- transition.setInterpolator(UiUtils.EASE_IN_INTERPOLATOR);
- transition.setEpicenterCallback(new EpicenterCallback() {
- @Override
- public Rect onGetEpicenter(final Transition transition) {
- return epicenter;
- }
- });
-
- // Kick off the delayed scene explode transition. Anything happens after this line in this
- // method before the next frame will be tracked by the transition manager for visibility
- // changes and animated accordingly.
- TransitionManager.beginDelayedTransition(mCustomHeaderViewPager,
- transition);
-
- toggleContactListItemsVisibilityForPendingTransition(show);
- }
-
- /**
- * Toggle the visibility of contact list items in the contact lists for them to be tracked by
- * the transition manager for pending explode transition.
- */
- private void toggleContactListItemsVisibilityForPendingTransition(final boolean show) {
- if (!OsUtil.isAtLeastL()) {
- // Explode animation is not supported pre-L.
- return;
- }
- mAllContactsListViewHolder.toggleVisibilityForPendingTransition(show, mPendingExplodeView);
- mFrequentContactsListViewHolder.toggleVisibilityForPendingTransition(show,
- mPendingExplodeView);
- }
-
- private void showHideContactPagerWithAnimation(final boolean show) {
- final boolean isPagerVisible = (mCustomHeaderViewPager.getVisibility() == View.VISIBLE);
- if (show == isPagerVisible) {
- return;
- }
-
- mCustomHeaderViewPager.animate().alpha(show ? 1F : 0F)
- .setStartDelay(!show ? UiUtils.COMPOSE_TRANSITION_DURATION : 0)
- .withStartAction(new Runnable() {
- @Override
- public void run() {
- mCustomHeaderViewPager.setVisibility(View.VISIBLE);
- mCustomHeaderViewPager.setAlpha(show ? 0F : 1F);
- }
- })
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- mCustomHeaderViewPager.setVisibility(show ? View.VISIBLE : View.GONE);
- mCustomHeaderViewPager.setAlpha(1F);
- }
- });
- }
-
- @Override
- public void onContactCustomColorLoaded(final ContactPickerData data) {
- mBinding.ensureBound(data);
- invalidateContactLists();
- }
-
- public void updateActionBar(final ActionBar actionBar) {
- // Hide the action bar for contact picker mode. The custom ToolBar containing chips UI
- // etc. will take the spot of the action bar.
- actionBar.hide();
- UiUtils.setStatusBarColor(getActivity(),
- getResources().getColor(R.color.compose_notification_bar_background));
- }
-
- private GetOrCreateConversationActionMonitor mMonitor;
-
- @Override
- @RunsOnMainThread
- public void onGetOrCreateConversationSucceeded(final ActionMonitor monitor,
- final Object data, final String conversationId) {
- Assert.isTrue(monitor == mMonitor);
- Assert.isTrue(conversationId != null);
-
- mRecipientTextView.setInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE |
- InputType.TYPE_CLASS_TEXT);
- mHost.onGetOrCreateNewConversation(conversationId);
-
- mMonitor = null;
- }
-
- @Override
- @RunsOnMainThread
- public void onGetOrCreateConversationFailed(final ActionMonitor monitor,
- final Object data) {
- Assert.isTrue(monitor == mMonitor);
- LogUtil.e(LogUtil.BUGLE_TAG, "onGetOrCreateConversationFailed");
- mMonitor = null;
- }
-}
diff --git a/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java b/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java
deleted file mode 100644
index 25f422e..0000000
--- a/src/com/android/messaging/ui/contact/ContactRecipientAdapter.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * 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.contact;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.database.MergeCursor;
-import android.support.v4.util.Pair;
-import android.text.TextUtils;
-import android.text.util.Rfc822Token;
-import android.text.util.Rfc822Tokenizer;
-import android.widget.Filter;
-
-import com.android.ex.chips.BaseRecipientAdapter;
-import com.android.ex.chips.RecipientAlternatesAdapter;
-import com.android.ex.chips.RecipientAlternatesAdapter.RecipientMatchCallback;
-import com.android.ex.chips.RecipientEntry;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.ContactRecipientEntryUtils;
-import com.android.messaging.util.ContactUtil;
-import com.android.messaging.util.PhoneUtils;
-
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
-/**
- * An extension on the base {@link BaseRecipientAdapter} that uses data layer from Bugle,
- * such as the ContactRecipientPhotoManager that uses our own MediaResourceManager, and
- * contact lookup that relies on ContactUtil. It provides data source and filtering ability
- * for {@link ContactRecipientAutoCompleteView}
- */
-public final class ContactRecipientAdapter extends BaseRecipientAdapter {
- public ContactRecipientAdapter(final Context context,
- final ContactListItemView.HostInterface clivHost) {
- this(context, Integer.MAX_VALUE, QUERY_TYPE_PHONE, clivHost);
- }
-
- public ContactRecipientAdapter(final Context context, final int preferredMaxResultCount,
- final int queryMode, final ContactListItemView.HostInterface clivHost) {
- super(context, preferredMaxResultCount, queryMode);
- setPhotoManager(new ContactRecipientPhotoManager(context, clivHost));
- }
-
- @Override
- public boolean forceShowAddress() {
- // We should always use the SingleRecipientAddressAdapter
- // And never use the RecipientAlternatesAdapter
- return true;
- }
-
- @Override
- public Filter getFilter() {
- return new ContactFilter();
- }
-
- /**
- * A Filter for a RecipientEditTextView that queries Bugle's ContactUtil for auto-complete
- * results.
- */
- public class ContactFilter extends Filter {
- // Used to sort filtered contacts when it has combined results from email and phone.
- private final RecipientEntryComparator mComparator = new RecipientEntryComparator();
-
- /**
- * Returns a cursor containing the filtered results in contacts given the search text,
- * and a boolean indicating whether the results are sorted.
- *
- * The queries are synchronously performed since this is not run on the main thread.
- *
- * Some locales (e.g. JPN) expect email addresses to be auto-completed for MMS.
- * If this is the case, perform two queries on phone number followed by email and
- * return the merged results.
- */
- @DoesNotRunOnMainThread
- private Pair<Cursor, Boolean> getFilteredResultsCursor(final Context context,
- final String searchText) {
- Assert.isNotMainThread();
- if (BugleGservices.get().getBoolean(
- BugleGservicesKeys.ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS,
- BugleGservicesKeys.ALWAYS_AUTOCOMPLETE_EMAIL_ADDRESS_DEFAULT)) {
- return Pair.create((Cursor) new MergeCursor(new Cursor[] {
- ContactUtil.filterPhones(getContext(), searchText)
- .performSynchronousQuery(),
- ContactUtil.filterEmails(getContext(), searchText)
- .performSynchronousQuery()
- }), false /* the merged cursor is not sorted */);
- } else {
- return Pair.create(ContactUtil.filterDestination(getContext(), searchText)
- .performSynchronousQuery(), true);
- }
- }
-
- @Override
- protected FilterResults performFiltering(final CharSequence constraint) {
- Assert.isNotMainThread();
- final FilterResults results = new FilterResults();
-
- // No query, return empty results.
- if (TextUtils.isEmpty(constraint)) {
- clearTempEntries();
- return results;
- }
-
- final String searchText = constraint.toString();
-
- // Query for auto-complete results, since performFiltering() is not done on the
- // main thread, perform the cursor loader queries directly.
- final Pair<Cursor, Boolean> filteredResults = getFilteredResultsCursor(getContext(),
- searchText);
- final Cursor cursor = filteredResults.first;
- final boolean sorted = filteredResults.second;
- if (cursor != null) {
- try {
- final List<RecipientEntry> entries = new ArrayList<RecipientEntry>();
-
- // First check if the constraint is a valid SMS destination. If so, add the
- // destination as a suggestion item to the drop down.
- if (PhoneUtils.isValidSmsMmsDestination(searchText)) {
- entries.add(ContactRecipientEntryUtils
- .constructSendToDestinationEntry(searchText));
- }
-
- HashSet<Long> existingContactIds = new HashSet<Long>();
- while (cursor.moveToNext()) {
- // Make sure there's only one first-level contact (i.e. contact for which
- // we show the avatar picture and name) for every contact id.
- final long contactId = cursor.getLong(ContactUtil.INDEX_CONTACT_ID);
- final boolean isFirstLevel = !existingContactIds.contains(contactId);
- if (isFirstLevel) {
- existingContactIds.add(contactId);
- }
- entries.add(ContactUtil.createRecipientEntryForPhoneQuery(cursor,
- isFirstLevel));
- }
-
- if (!sorted) {
- Collections.sort(entries, mComparator);
- }
- results.values = entries;
- results.count = 1;
-
- } finally {
- cursor.close();
- }
- }
- return results;
- }
-
- @Override
- protected void publishResults(final CharSequence constraint, final FilterResults results) {
- mCurrentConstraint = constraint;
- clearTempEntries();
-
- if (results.values != null) {
- @SuppressWarnings("unchecked")
- final List<RecipientEntry> entries = (List<RecipientEntry>) results.values;
- updateEntries(entries);
- } else {
- updateEntries(Collections.<RecipientEntry>emptyList());
- }
- }
-
- private class RecipientEntryComparator implements Comparator<RecipientEntry> {
- private final Collator mCollator;
-
- public RecipientEntryComparator() {
- mCollator = Collator.getInstance(Locale.getDefault());
- mCollator.setStrength(Collator.PRIMARY);
- }
-
- /**
- * Compare two RecipientEntry's, first by locale-aware display name comparison, then by
- * contact id comparison, finally by first-level-ness comparison.
- */
- @Override
- public int compare(RecipientEntry lhs, RecipientEntry rhs) {
- // Send-to-destinations always appear before everything else.
- final boolean sendToLhs = ContactRecipientEntryUtils
- .isSendToDestinationContact(lhs);
- final boolean sendToRhs = ContactRecipientEntryUtils
- .isSendToDestinationContact(lhs);
- if (sendToLhs != sendToRhs) {
- if (sendToLhs) {
- return -1;
- } else if (sendToRhs) {
- return 1;
- }
- }
-
- final int displayNameCompare = mCollator.compare(lhs.getDisplayName(),
- rhs.getDisplayName());
- if (displayNameCompare != 0) {
- return displayNameCompare;
- }
-
- // Long.compare could accomplish the following three lines, but this is only
- // available in API 19+
- final long lhsContactId = lhs.getContactId();
- final long rhsContactId = rhs.getContactId();
- final int contactCompare = lhsContactId < rhsContactId ? -1 :
- (lhsContactId == rhsContactId ? 0 : 1);
- if (contactCompare != 0) {
- return contactCompare;
- }
-
- // These are the same contact. Make sure first-level contacts always
- // appear at the front.
- if (lhs.isFirstLevel()) {
- return -1;
- } else if (rhs.isFirstLevel()) {
- return 1;
- } else {
- return 0;
- }
- }
- }
- }
-
- /**
- * Called when we need to substitute temporary recipient chips with better alternatives.
- * For example, if a list of comma-delimited phone numbers are pasted into the edit box,
- * we want to be able to look up in the ContactUtil for exact matches and get contact
- * details such as name and photo thumbnail for the contact to display a better chip.
- */
- @Override
- public void getMatchingRecipients(final ArrayList<String> inAddresses,
- final RecipientMatchCallback callback) {
- final int addressesSize = Math.min(
- RecipientAlternatesAdapter.MAX_LOOKUPS, inAddresses.size());
- final HashSet<String> addresses = new HashSet<String>();
- for (int i = 0; i < addressesSize; i++) {
- final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(inAddresses.get(i).toLowerCase());
- addresses.add(tokens.length > 0 ? tokens[0].getAddress() : inAddresses.get(i));
- }
-
- final Map<String, RecipientEntry> recipientEntries =
- new HashMap<String, RecipientEntry>();
- // query for each address
- for (final String address : addresses) {
- final Cursor cursor = ContactUtil.lookupDestination(getContext(), address)
- .performSynchronousQuery();
- if (cursor != null) {
- try {
- if (cursor.moveToNext()) {
- // There may be multiple matches to the same number, always take the
- // first match.
- // TODO: May need to consider if there's an existing conversation
- // that matches this particular contact and prioritize that contact.
- final RecipientEntry entry =
- ContactUtil.createRecipientEntryForPhoneQuery(cursor, true);
- recipientEntries.put(address, entry);
- }
-
- } finally {
- cursor.close();
- }
- }
- }
-
- // report matches
- callback.matchesFound(recipientEntries);
- }
-}
diff --git a/src/com/android/messaging/ui/contact/ContactRecipientAutoCompleteView.java b/src/com/android/messaging/ui/contact/ContactRecipientAutoCompleteView.java
deleted file mode 100644
index c7c2731..0000000
--- a/src/com/android/messaging/ui/contact/ContactRecipientAutoCompleteView.java
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * 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.contact;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Rect;
-import android.os.AsyncTask;
-import android.support.v7.appcompat.R;
-import android.text.Editable;
-import android.text.TextPaint;
-import android.text.TextWatcher;
-import android.text.util.Rfc822Tokenizer;
-import android.util.AttributeSet;
-import android.view.ContextThemeWrapper;
-import android.view.KeyEvent;
-import android.view.inputmethod.EditorInfo;
-import android.widget.TextView;
-
-import com.android.ex.chips.RecipientEditTextView;
-import com.android.ex.chips.RecipientEntry;
-import com.android.ex.chips.recipientchip.DrawableRecipientChip;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.util.ContactRecipientEntryUtils;
-import com.android.messaging.util.ContactUtil;
-import com.android.messaging.util.PhoneUtils;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-
-/**
- * An extension for {@link RecipientEditTextView} which shows a list of Materialized contact chips.
- * It uses Bugle's ContactUtil to perform contact lookup, and is able to return the list of
- * recipients in the form of a ParticipantData list.
- */
-public class ContactRecipientAutoCompleteView extends RecipientEditTextView {
- public interface ContactChipsChangeListener {
- void onContactChipsChanged(int oldCount, int newCount);
- void onInvalidContactChipsPruned(int prunedCount);
- void onEntryComplete();
- }
-
- private final int mTextHeight;
- private ContactChipsChangeListener mChipsChangeListener;
-
- /**
- * Watches changes in contact chips to determine possible state transitions.
- */
- private class ContactChipsWatcher implements TextWatcher {
- /**
- * Tracks the old chips count before text changes. Note that we currently don't compare
- * the entire chip sets but just the cheaper-to-do before and after counts, because
- * the chips view don't allow for replacing chips.
- */
- private int mLastChipsCount = 0;
-
- @Override
- public void onTextChanged(final CharSequence s, final int start, final int before,
- final int count) {
- }
-
- @Override
- public void beforeTextChanged(final CharSequence s, final int start, final int count,
- final int after) {
- // We don't take mLastChipsCount from here but from the last afterTextChanged() run.
- // The reason is because at this point, any chip spans to be removed is already removed
- // from s in the chips text view.
- }
-
- @Override
- public void afterTextChanged(final Editable s) {
- final int currentChipsCount = s.getSpans(0, s.length(),
- DrawableRecipientChip.class).length;
- if (currentChipsCount != mLastChipsCount) {
- // When a sanitizing task is running, we don't want to notify any chips count
- // change, but we do want to track the last chip count.
- if (mChipsChangeListener != null && mCurrentSanitizeTask == null) {
- mChipsChangeListener.onContactChipsChanged(mLastChipsCount, currentChipsCount);
- }
- mLastChipsCount = currentChipsCount;
- }
- }
- }
-
- private static final String TEXT_HEIGHT_SAMPLE = "a";
-
- public ContactRecipientAutoCompleteView(final Context context, final AttributeSet attrs) {
- super(new ContextThemeWrapper(context, R.style.ColorAccentGrayOverrideStyle), attrs);
-
- // Get the height of the text, given the currently set font face and size.
- final Rect textBounds = new Rect(0, 0, 0, 0);
- final TextPaint paint = getPaint();
- paint.getTextBounds(TEXT_HEIGHT_SAMPLE, 0, TEXT_HEIGHT_SAMPLE.length(), textBounds);
- mTextHeight = textBounds.height();
-
- setTokenizer(new Rfc822Tokenizer());
- addTextChangedListener(new ContactChipsWatcher());
- setOnFocusListShrinkRecipients(false);
-
- setBackground(context.getResources().getDrawable(
- R.drawable.abc_textfield_search_default_mtrl_alpha));
- }
-
- public void setContactChipsListener(final ContactChipsChangeListener listener) {
- mChipsChangeListener = listener;
- }
-
- /**
- * A tuple of chips which AsyncContactChipSanitizeTask reports as progress to have the
- * chip actually replaced/removed on the UI thread.
- */
- private class ChipReplacementTuple {
- public final DrawableRecipientChip removedChip;
- public final RecipientEntry replacedChipEntry;
-
- public ChipReplacementTuple(final DrawableRecipientChip removedChip,
- final RecipientEntry replacedChipEntry) {
- this.removedChip = removedChip;
- this.replacedChipEntry = replacedChipEntry;
- }
- }
-
- /**
- * An AsyncTask that cleans up contact chips on every chips commit (i.e. get or create a new
- * conversation with the given chips).
- */
- private class AsyncContactChipSanitizeTask extends
- AsyncTask<Void, ChipReplacementTuple, Integer> {
-
- @Override
- protected Integer doInBackground(final Void... params) {
- final DrawableRecipientChip[] recips = getText()
- .getSpans(0, getText().length(), DrawableRecipientChip.class);
- int invalidChipsRemoved = 0;
- for (final DrawableRecipientChip recipient : recips) {
- final RecipientEntry entry = recipient.getEntry();
- if (entry != null) {
- if (entry.isValid()) {
- if (RecipientEntry.isCreatedRecipient(entry.getContactId()) ||
- ContactRecipientEntryUtils.isSendToDestinationContact(entry)) {
- // This is a generated/send-to contact chip, try to look it up and
- // display a chip for the corresponding local contact.
- final Cursor lookupResult = ContactUtil.lookupDestination(getContext(),
- entry.getDestination()).performSynchronousQuery();
- if (lookupResult != null && lookupResult.moveToNext()) {
- // Found a match, remove the generated entry and replace with
- // a better local entry.
- publishProgress(new ChipReplacementTuple(recipient,
- ContactUtil.createRecipientEntryForPhoneQuery(
- lookupResult, true)));
- } else if (PhoneUtils.isValidSmsMmsDestination(
- entry.getDestination())){
- // No match was found, but we have a valid destination so let's at
- // least create an entry that shows an avatar.
- publishProgress(new ChipReplacementTuple(recipient,
- ContactRecipientEntryUtils.constructNumberWithAvatarEntry(
- entry.getDestination())));
- } else {
- // Not a valid contact. Remove and show an error.
- publishProgress(new ChipReplacementTuple(recipient, null));
- invalidChipsRemoved++;
- }
- }
- } else {
- publishProgress(new ChipReplacementTuple(recipient, null));
- invalidChipsRemoved++;
- }
- }
- }
- return invalidChipsRemoved;
- }
-
- @Override
- protected void onProgressUpdate(final ChipReplacementTuple... values) {
- for (final ChipReplacementTuple tuple : values) {
- if (tuple.removedChip != null) {
- final Editable text = getText();
- final int chipStart = text.getSpanStart(tuple.removedChip);
- final int chipEnd = text.getSpanEnd(tuple.removedChip);
- if (chipStart >= 0 && chipEnd >= 0) {
- text.delete(chipStart, chipEnd);
- }
-
- if (tuple.replacedChipEntry != null) {
- appendRecipientEntry(tuple.replacedChipEntry);
- }
- }
- }
- }
-
- @Override
- protected void onPostExecute(final Integer invalidChipsRemoved) {
- mCurrentSanitizeTask = null;
- if (invalidChipsRemoved > 0) {
- mChipsChangeListener.onInvalidContactChipsPruned(invalidChipsRemoved);
- }
- }
- }
-
- /**
- * We don't use SafeAsyncTask but instead use a single threaded executor to ensure that
- * all sanitization tasks are serially executed so as not to interfere with each other.
- */
- private static final Executor SANITIZE_EXECUTOR = Executors.newSingleThreadExecutor();
-
- private AsyncContactChipSanitizeTask mCurrentSanitizeTask;
-
- /**
- * Whenever the caller wants to start a new conversation with the list of chips we have,
- * make sure we asynchronously:
- * 1. Remove invalid chips.
- * 2. Attempt to resolve unknown contacts to known local contacts.
- * 3. Convert still unknown chips to chips with generated avatar.
- *
- * Note that we don't need to perform this synchronously since we can
- * resolve any unknown contacts to local contacts when needed.
- */
- private void sanitizeContactChips() {
- if (mCurrentSanitizeTask != null && !mCurrentSanitizeTask.isCancelled()) {
- mCurrentSanitizeTask.cancel(false);
- mCurrentSanitizeTask = null;
- }
- mCurrentSanitizeTask = new AsyncContactChipSanitizeTask();
- mCurrentSanitizeTask.executeOnExecutor(SANITIZE_EXECUTOR);
- }
-
- /**
- * Returns a list of ParticipantData from the entered chips in order to create
- * new conversation.
- */
- public ArrayList<ParticipantData> getRecipientParticipantDataForConversationCreation() {
- final DrawableRecipientChip[] recips = getText()
- .getSpans(0, getText().length(), DrawableRecipientChip.class);
- final ArrayList<ParticipantData> contacts =
- new ArrayList<ParticipantData>(recips.length);
- for (final DrawableRecipientChip recipient : recips) {
- final RecipientEntry entry = recipient.getEntry();
- if (entry != null && entry.isValid() && entry.getDestination() != null &&
- PhoneUtils.isValidSmsMmsDestination(entry.getDestination())) {
- contacts.add(ParticipantData.getFromRecipientEntry(recipient.getEntry()));
- }
- }
- sanitizeContactChips();
- return contacts;
- }
-
- /**c
- * Gets a set of currently selected chips' emails/phone numbers. This will facilitate the
- * consumer with determining quickly whether a contact is currently selected.
- */
- public Set<String> getSelectedDestinations() {
- Set<String> set = new HashSet<String>();
- final DrawableRecipientChip[] recips = getText()
- .getSpans(0, getText().length(), DrawableRecipientChip.class);
-
- for (final DrawableRecipientChip recipient : recips) {
- final RecipientEntry entry = recipient.getEntry();
- if (entry != null && entry.isValid() && entry.getDestination() != null) {
- set.add(PhoneUtils.getDefault().getCanonicalBySystemLocale(
- entry.getDestination()));
- }
- }
- return set;
- }
-
- @Override
- public boolean onEditorAction(final TextView view, final int actionId, final KeyEvent event) {
- if (actionId == EditorInfo.IME_ACTION_DONE) {
- mChipsChangeListener.onEntryComplete();
- }
- return super.onEditorAction(view, actionId, event);
- }
-}
diff --git a/src/com/android/messaging/ui/contact/ContactRecipientPhotoManager.java b/src/com/android/messaging/ui/contact/ContactRecipientPhotoManager.java
deleted file mode 100644
index d69ba64..0000000
--- a/src/com/android/messaging/ui/contact/ContactRecipientPhotoManager.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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.contact;
-
-import android.content.Context;
-import android.net.Uri;
-
-import com.android.ex.chips.PhotoManager;
-import com.android.ex.chips.RecipientEntry;
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.media.AvatarRequestDescriptor;
-import com.android.messaging.datamodel.media.BindableMediaRequest;
-import com.android.messaging.datamodel.media.ImageResource;
-import com.android.messaging.datamodel.media.MediaRequest;
-import com.android.messaging.datamodel.media.MediaResourceManager;
-import com.android.messaging.datamodel.media.MediaResourceManager.MediaResourceLoadListener;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.ThreadUtil;
-
-/**
- * An implementation of {@link PhotoManager} that hooks up the chips UI's photos with our own
- * {@link MediaResourceManager} for retrieving and caching contact avatars.
- */
-public class ContactRecipientPhotoManager implements PhotoManager {
- private static final String IMAGE_BYTES_REQUEST_STATIC_BINDING_ID = "imagebytes";
- private final Context mContext;
- private final int mIconSize;
- private final ContactListItemView.HostInterface mClivHostInterface;
-
- public ContactRecipientPhotoManager(final Context context,
- final ContactListItemView.HostInterface clivHostInterface) {
- mContext = context;
- mIconSize = context.getResources().getDimensionPixelSize(
- R.dimen.compose_message_chip_height) - context.getResources().getDimensionPixelSize(
- R.dimen.compose_message_chip_padding) * 2;
- mClivHostInterface = clivHostInterface;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void populatePhotoBytesAsync(final RecipientEntry entry,
- final PhotoManagerCallback callback) {
- // Post all media resource request to the main thread.
- ThreadUtil.getMainThreadHandler().post(new Runnable() {
- @Override
- public void run() {
- final Uri avatarUri = AvatarUriUtil.createAvatarUri(
- ParticipantData.getFromRecipientEntry(entry));
- final AvatarRequestDescriptor descriptor =
- new AvatarRequestDescriptor(avatarUri, mIconSize, mIconSize);
- final BindableMediaRequest<ImageResource> req = descriptor.buildAsyncMediaRequest(
- mContext,
- new MediaResourceLoadListener<ImageResource>() {
- @Override
- public void onMediaResourceLoaded(final MediaRequest<ImageResource> request,
- final ImageResource resource, final boolean isCached) {
- entry.setPhotoBytes(resource.getBytes());
- callback.onPhotoBytesAsynchronouslyPopulated();
- }
-
- @Override
- public void onMediaResourceLoadError(final MediaRequest<ImageResource> request,
- final Exception exception) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Photo bytes loading failed due to " +
- exception + " request key=" + request.getKey());
-
- // Fall back to the default avatar image.
- callback.onPhotoBytesAsyncLoadFailed();
- }});
-
- // Statically bind the request since it's not bound to any specific piece of UI.
- req.bind(IMAGE_BYTES_REQUEST_STATIC_BINDING_ID);
-
- Factory.get().getMediaResourceManager().requestMediaResourceAsync(req);
- }
- });
- }
-}
diff --git a/src/com/android/messaging/ui/contact/ContactSectionIndexer.java b/src/com/android/messaging/ui/contact/ContactSectionIndexer.java
deleted file mode 100644
index 1d5abf3..0000000
--- a/src/com/android/messaging/ui/contact/ContactSectionIndexer.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * 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.contact;
-
-import android.database.Cursor;
-import android.os.Bundle;
-import android.provider.ContactsContract.Contacts;
-import android.text.TextUtils;
-import android.widget.SectionIndexer;
-
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContactUtil;
-import com.android.messaging.util.LogUtil;
-
-import java.util.ArrayList;
-
-/**
- * Indexes contact alphabetical sections so we can report to the fast scrolling list view
- * where we are in the list when the user scrolls through the contact list, allowing us to show
- * alphabetical indicators for the fast scroller as well as list section headers.
- */
-public class ContactSectionIndexer implements SectionIndexer {
- private String[] mSections;
- private ArrayList<Integer> mSectionStartingPositions;
- private static final String BLANK_HEADER_STRING = " ";
-
- public ContactSectionIndexer(final Cursor contactsCursor) {
- buildIndexer(contactsCursor);
- }
-
- @Override
- public Object[] getSections() {
- return mSections;
- }
-
- @Override
- public int getPositionForSection(final int sectionIndex) {
- if (mSectionStartingPositions.isEmpty()) {
- return 0;
- }
- // Clamp to the bounds of the section position array per Android API doc.
- return mSectionStartingPositions.get(
- Math.max(Math.min(sectionIndex, mSectionStartingPositions.size() - 1), 0));
- }
-
- @Override
- public int getSectionForPosition(final int position) {
- if (mSectionStartingPositions.isEmpty()) {
- return 0;
- }
-
- // Perform a binary search on the starting positions of the sections to the find the
- // section for the position.
- int left = 0;
- int right = mSectionStartingPositions.size() - 1;
-
- // According to getSectionForPosition()'s doc, we should always clamp the value when the
- // position is out of bound.
- if (position <= mSectionStartingPositions.get(left)) {
- return left;
- } else if (position >= mSectionStartingPositions.get(right)) {
- return right;
- }
-
- while (left <= right) {
- final int mid = (left + right) / 2;
- final int startingPos = mSectionStartingPositions.get(mid);
- final int nextStartingPos = mSectionStartingPositions.get(mid + 1);
- if (position >= startingPos && position < nextStartingPos) {
- return mid;
- } else if (position < startingPos) {
- right = mid - 1;
- } else if (position >= nextStartingPos) {
- left = mid + 1;
- }
- }
- Assert.fail("Invalid section indexer state: couldn't find section for pos " + position);
- return -1;
- }
-
- private boolean buildIndexerFromCursorExtras(final Cursor cursor) {
- if (cursor == null) {
- return false;
- }
- final Bundle cursorExtras = cursor.getExtras();
- if (cursorExtras == null) {
- return false;
- }
- final String[] sections = cursorExtras.getStringArray(
- Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
- final int[] counts = cursorExtras.getIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
- if (sections == null || counts == null) {
- return false;
- }
-
- if (sections.length != counts.length) {
- return false;
- }
-
- this.mSections = sections;
- mSectionStartingPositions = new ArrayList<Integer>(counts.length);
- int position = 0;
- for (int i = 0; i < counts.length; i++) {
- if (TextUtils.isEmpty(mSections[i])) {
- mSections[i] = BLANK_HEADER_STRING;
- } else if (!mSections[i].equals(BLANK_HEADER_STRING)) {
- mSections[i] = mSections[i].trim();
- }
-
- mSectionStartingPositions.add(position);
- position += counts[i];
- }
- return true;
- }
-
- private void buildIndexerFromDisplayNames(final Cursor cursor) {
- // Loop through the contact cursor and get the starting position for each first character.
- // The result is stored into two arrays, one for the section header (i.e. the first
- // character), and one for the starting position, which is guaranteed to be sorted in
- // ascending order.
- final ArrayList<String> sections = new ArrayList<String>();
- mSectionStartingPositions = new ArrayList<Integer>();
- if (cursor != null) {
- cursor.moveToPosition(-1);
- int currentPosition = 0;
- while (cursor.moveToNext()) {
- // The sort key is typically the contact's display name, so for example, a contact
- // named "Bob" will go into section "B". The Contacts provider generally uses a
- // a slightly more sophisticated heuristic, but as a fallback this is good enough.
- final String sortKey = cursor.getString(ContactUtil.INDEX_SORT_KEY);
- final String section = TextUtils.isEmpty(sortKey) ? BLANK_HEADER_STRING :
- sortKey.substring(0, 1).toUpperCase();
-
- final int lastIndex = sections.size() - 1;
- final String currentSection = lastIndex >= 0 ? sections.get(lastIndex) : null;
- if (!TextUtils.equals(currentSection, section)) {
- sections.add(section);
- mSectionStartingPositions.add(currentPosition);
- }
- currentPosition++;
- }
- }
- mSections = new String[sections.size()];
- sections.toArray(mSections);
- }
-
- private void buildIndexer(final Cursor cursor) {
- // First check if we get indexer label extras from the contact provider; if not, fall back
- // to building from display names.
- if (!buildIndexerFromCursorExtras(cursor)) {
- LogUtil.w(LogUtil.BUGLE_TAG, "contact provider didn't provide contact label " +
- "information, fall back to using display name!");
- buildIndexerFromDisplayNames(cursor);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/contact/FrequentContactsListViewHolder.java b/src/com/android/messaging/ui/contact/FrequentContactsListViewHolder.java
deleted file mode 100644
index 1f3c795..0000000
--- a/src/com/android/messaging/ui/contact/FrequentContactsListViewHolder.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.contact;
-
-import android.content.Context;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.CustomHeaderPagerListViewHolder;
-import com.android.messaging.ui.contact.ContactListItemView.HostInterface;
-
-/**
- * Holds the frequent contacts view for the contact picker's view pager.
- */
-public class FrequentContactsListViewHolder extends CustomHeaderPagerListViewHolder {
- public FrequentContactsListViewHolder(final Context context,
- final HostInterface clivHostInterface) {
- super(context, new ContactListAdapter(context, null, clivHostInterface,
- false /* needAlphabetHeader */));
- }
-
- @Override
- protected int getLayoutResId() {
- return R.layout.frequent_contacts_list_view;
- }
-
- @Override
- protected int getPageTitleResId() {
- return R.string.contact_picker_frequents_tab_title;
- }
-
- @Override
- protected int getEmptyViewResId() {
- return R.id.empty_view;
- }
-
- @Override
- protected int getListViewResId() {
- return R.id.frequent_contacts_list;
- }
-
- @Override
- protected int getEmptyViewTitleResId() {
- return R.string.contact_list_empty_text;
- }
-
- @Override
- protected int getEmptyViewImageResId() {
- return R.drawable.ic_oobe_freq_list;
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ComposeMessageView.java b/src/com/android/messaging/ui/conversation/ComposeMessageView.java
deleted file mode 100644
index 17f8f74..0000000
--- a/src/com/android/messaging/ui/conversation/ComposeMessageView.java
+++ /dev/null
@@ -1,962 +0,0 @@
-/*
- * 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.content.Context;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.text.Editable;
-import android.text.Html;
-import android.text.InputFilter;
-import android.text.InputFilter.LengthFilter;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.util.AttributeSet;
-import android.view.ContextThemeWrapper;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.inputmethod.EditorInfo;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.binding.ImmutableBindingRef;
-import com.android.messaging.datamodel.data.ConversationData;
-import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener;
-import com.android.messaging.datamodel.data.ConversationData.SimpleConversationDataListener;
-import com.android.messaging.datamodel.data.DraftMessageData;
-import com.android.messaging.datamodel.data.DraftMessageData.CheckDraftForSendTask;
-import com.android.messaging.datamodel.data.DraftMessageData.CheckDraftTaskCallback;
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.data.PendingAttachmentData;
-import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.ui.AttachmentPreview;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.PlainTextEditText;
-import com.android.messaging.ui.conversation.ConversationInputManager.ConversationInputSink;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.MediaUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.UiUtils;
-
-import java.util.Collection;
-import java.util.List;
-
-/**
- * This view contains the UI required to generate and send messages.
- */
-public class ComposeMessageView extends LinearLayout
- implements TextView.OnEditorActionListener, DraftMessageDataListener, TextWatcher,
- ConversationInputSink {
-
- public interface IComposeMessageViewHost extends
- DraftMessageData.DraftMessageSubscriptionDataProvider {
- void sendMessage(MessageData message);
- void onComposeEditTextFocused();
- void onAttachmentsCleared();
- void onAttachmentsChanged(final boolean haveAttachments);
- void displayPhoto(Uri photoUri, Rect imageBounds, boolean isDraft);
- void promptForSelfPhoneNumber();
- boolean isReadyForAction();
- void warnOfMissingActionConditions(final boolean sending,
- final Runnable commandToRunAfterActionConditionResolved);
- void warnOfExceedingMessageLimit(final boolean showAttachmentChooser,
- boolean tooManyVideos);
- void notifyOfAttachmentLoadFailed();
- void showAttachmentChooser();
- boolean shouldShowSubjectEditor();
- boolean shouldHideAttachmentsWhenSimSelectorShown();
- Uri getSelfSendButtonIconUri();
- int overrideCounterColor();
- int getAttachmentsClearedFlags();
- }
-
- public static final int CODEPOINTS_REMAINING_BEFORE_COUNTER_SHOWN = 10;
-
- // There is no draft and there is no need for the SIM selector
- private static final int SEND_WIDGET_MODE_SELF_AVATAR = 1;
- // There is no draft but we need to show the SIM selector
- private static final int SEND_WIDGET_MODE_SIM_SELECTOR = 2;
- // There is a draft
- private static final int SEND_WIDGET_MODE_SEND_BUTTON = 3;
-
- private PlainTextEditText mComposeEditText;
- private PlainTextEditText mComposeSubjectText;
- private TextView mCharCounter;
- private TextView mMmsIndicator;
- private SimIconView mSelfSendIcon;
- private ImageButton mSendButton;
- private View mSubjectView;
- private ImageButton mDeleteSubjectButton;
- private AttachmentPreview mAttachmentPreview;
- private ImageButton mAttachMediaButton;
-
- private final Binding<DraftMessageData> mBinding;
- private IComposeMessageViewHost mHost;
- private final Context mOriginalContext;
- private int mSendWidgetMode = SEND_WIDGET_MODE_SELF_AVATAR;
-
- // Shared data model object binding from the conversation.
- private ImmutableBindingRef<ConversationData> mConversationDataModel;
-
- // Centrally manages all the mutual exclusive UI components accepting user input, i.e.
- // media picker, IME keyboard and SIM selector.
- private ConversationInputManager mInputManager;
-
- private final ConversationDataListener mDataListener = new SimpleConversationDataListener() {
- @Override
- public void onConversationMetadataUpdated(ConversationData data) {
- mConversationDataModel.ensureBound(data);
- updateVisualsOnDraftChanged();
- }
-
- @Override
- public void onConversationParticipantDataLoaded(ConversationData data) {
- mConversationDataModel.ensureBound(data);
- updateVisualsOnDraftChanged();
- }
-
- @Override
- public void onSubscriptionListDataLoaded(ConversationData data) {
- mConversationDataModel.ensureBound(data);
- updateOnSelfSubscriptionChange();
- updateVisualsOnDraftChanged();
- }
- };
-
- public ComposeMessageView(final Context context, final AttributeSet attrs) {
- super(new ContextThemeWrapper(context, R.style.ColorAccentBlueOverrideStyle), attrs);
- mOriginalContext = context;
- mBinding = BindingBase.createBinding(this);
- }
-
- /**
- * Host calls this to bind view to DraftMessageData object
- */
- public void bind(final DraftMessageData data, final IComposeMessageViewHost host) {
- mHost = host;
- mBinding.bind(data);
- data.addListener(this);
- data.setSubscriptionDataProvider(host);
-
- final int counterColor = mHost.overrideCounterColor();
- if (counterColor != -1) {
- mCharCounter.setTextColor(counterColor);
- }
- }
-
- /**
- * Host calls this to unbind view
- */
- public void unbind() {
- mBinding.unbind();
- mHost = null;
- mInputManager.onDetach();
- }
-
- @Override
- protected void onFinishInflate() {
- mComposeEditText = (PlainTextEditText) findViewById(
- R.id.compose_message_text);
- mComposeEditText.setOnEditorActionListener(this);
- mComposeEditText.addTextChangedListener(this);
- mComposeEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
- @Override
- public void onFocusChange(final View v, final boolean hasFocus) {
- if (v == mComposeEditText && hasFocus) {
- mHost.onComposeEditTextFocused();
- }
- }
- });
- mComposeEditText.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View arg0) {
- if (mHost.shouldHideAttachmentsWhenSimSelectorShown()) {
- hideSimSelector();
- }
- }
- });
-
- // onFinishInflate() is called before self is loaded from db. We set the default text
- // limit here, and apply the real limit later in updateOnSelfSubscriptionChange().
- mComposeEditText.setFilters(new InputFilter[] {
- new LengthFilter(MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID)
- .getMaxTextLimit()) });
-
- mSelfSendIcon = (SimIconView) findViewById(R.id.self_send_icon);
- mSelfSendIcon.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- boolean shown = mInputManager.toggleSimSelector(true /* animate */,
- getSelfSubscriptionListEntry());
- hideAttachmentsWhenShowingSims(shown);
- }
- });
- mSelfSendIcon.setOnLongClickListener(new OnLongClickListener() {
- @Override
- public boolean onLongClick(final View v) {
- if (mHost.shouldShowSubjectEditor()) {
- showSubjectEditor();
- } else {
- boolean shown = mInputManager.toggleSimSelector(true /* animate */,
- getSelfSubscriptionListEntry());
- hideAttachmentsWhenShowingSims(shown);
- }
- return true;
- }
- });
-
- mComposeSubjectText = (PlainTextEditText) findViewById(
- R.id.compose_subject_text);
- // We need the listener to change the avatar to the send button when the user starts
- // typing a subject without a message.
- mComposeSubjectText.addTextChangedListener(this);
- // onFinishInflate() is called before self is loaded from db. We set the default text
- // limit here, and apply the real limit later in updateOnSelfSubscriptionChange().
- mComposeSubjectText.setFilters(new InputFilter[] {
- new LengthFilter(MmsConfig.get(ParticipantData.DEFAULT_SELF_SUB_ID)
- .getMaxSubjectLength())});
-
- mDeleteSubjectButton = (ImageButton) findViewById(R.id.delete_subject_button);
- mDeleteSubjectButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View clickView) {
- hideSubjectEditor();
- mComposeSubjectText.setText(null);
- mBinding.getData().setMessageSubject(null);
- }
- });
-
- mSubjectView = findViewById(R.id.subject_view);
-
- mSendButton = (ImageButton) findViewById(R.id.send_message_button);
- mSendButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View clickView) {
- sendMessageInternal(true /* checkMessageSize */);
- }
- });
- mSendButton.setOnLongClickListener(new OnLongClickListener() {
- @Override
- public boolean onLongClick(final View arg0) {
- boolean shown = mInputManager.toggleSimSelector(true /* animate */,
- getSelfSubscriptionListEntry());
- hideAttachmentsWhenShowingSims(shown);
- if (mHost.shouldShowSubjectEditor()) {
- showSubjectEditor();
- }
- return true;
- }
- });
- mSendButton.setAccessibilityDelegate(new AccessibilityDelegate() {
- @Override
- public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(host, event);
- // When the send button is long clicked, we want TalkBack to announce the real
- // action (select SIM or edit subject), as opposed to "long press send button."
- if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_LONG_CLICKED) {
- event.getText().clear();
- event.getText().add(getResources()
- .getText(shouldShowSimSelector(mConversationDataModel.getData()) ?
- R.string.send_button_long_click_description_with_sim_selector :
- R.string.send_button_long_click_description_no_sim_selector));
- // Make this an announcement so TalkBack will read our custom message.
- event.setEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT);
- }
- }
- });
-
- mAttachMediaButton =
- (ImageButton) findViewById(R.id.attach_media_button);
- mAttachMediaButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View clickView) {
- // Showing the media picker is treated as starting to compose the message.
- mInputManager.showHideMediaPicker(true /* show */, true /* animate */);
- }
- });
-
- mAttachmentPreview = (AttachmentPreview) findViewById(R.id.attachment_draft_view);
- mAttachmentPreview.setComposeMessageView(this);
-
- mCharCounter = (TextView) findViewById(R.id.char_counter);
- mMmsIndicator = (TextView) findViewById(R.id.mms_indicator);
- }
-
- private void hideAttachmentsWhenShowingSims(final boolean simPickerVisible) {
- if (!mHost.shouldHideAttachmentsWhenSimSelectorShown()) {
- return;
- }
- final boolean haveAttachments = mBinding.getData().hasAttachments();
- if (simPickerVisible && haveAttachments) {
- mHost.onAttachmentsChanged(false);
- mAttachmentPreview.hideAttachmentPreview();
- } else {
- mHost.onAttachmentsChanged(haveAttachments);
- mAttachmentPreview.onAttachmentsChanged(mBinding.getData());
- }
- }
-
- public void setInputManager(final ConversationInputManager inputManager) {
- mInputManager = inputManager;
- }
-
- public void setConversationDataModel(final ImmutableBindingRef<ConversationData> refDataModel) {
- mConversationDataModel = refDataModel;
- mConversationDataModel.getData().addConversationDataListener(mDataListener);
- }
-
- ImmutableBindingRef<DraftMessageData> getDraftDataModel() {
- return BindingBase.createBindingReference(mBinding);
- }
-
- // returns true if it actually shows the subject editor and false if already showing
- private boolean showSubjectEditor() {
- // show the subject editor
- if (mSubjectView.getVisibility() == View.GONE) {
- mSubjectView.setVisibility(View.VISIBLE);
- mSubjectView.requestFocus();
- return true;
- }
- return false;
- }
-
- private void hideSubjectEditor() {
- mSubjectView.setVisibility(View.GONE);
- mComposeEditText.requestFocus();
- }
-
- /**
- * {@inheritDoc} from TextView.OnEditorActionListener
- */
- @Override // TextView.OnEditorActionListener.onEditorAction
- public boolean onEditorAction(final TextView view, final int actionId, final KeyEvent event) {
- if (actionId == EditorInfo.IME_ACTION_SEND) {
- sendMessageInternal(true /* checkMessageSize */);
- return true;
- }
- return false;
- }
-
- private void sendMessageInternal(final boolean checkMessageSize) {
- LogUtil.i(LogUtil.BUGLE_TAG, "UI initiated message sending in conversation " +
- mBinding.getData().getConversationId());
- if (mBinding.getData().isCheckingDraft()) {
- // Don't send message if we are currently checking draft for sending.
- LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: still checking draft");
- return;
- }
- // Check the host for pre-conditions about any action.
- if (mHost.isReadyForAction()) {
- mInputManager.showHideSimSelector(false /* show */, true /* animate */);
- final String messageToSend = mComposeEditText.getText().toString();
- mBinding.getData().setMessageText(messageToSend);
- final String subject = mComposeSubjectText.getText().toString();
- mBinding.getData().setMessageSubject(subject);
- // Asynchronously check the draft against various requirements before sending.
- mBinding.getData().checkDraftForAction(checkMessageSize,
- mHost.getConversationSelfSubId(), new CheckDraftTaskCallback() {
- @Override
- public void onDraftChecked(DraftMessageData data, int result) {
- mBinding.ensureBound(data);
- switch (result) {
- case CheckDraftForSendTask.RESULT_PASSED:
- // Continue sending after check succeeded.
- final MessageData message = mBinding.getData()
- .prepareMessageForSending(mBinding);
- if (message != null && message.hasContent()) {
- playSentSound();
- mHost.sendMessage(message);
- hideSubjectEditor();
- if (AccessibilityUtil.isTouchExplorationEnabled(getContext())) {
- AccessibilityUtil.announceForAccessibilityCompat(
- ComposeMessageView.this, null,
- R.string.sending_message);
- }
- }
- break;
-
- case CheckDraftForSendTask.RESULT_HAS_PENDING_ATTACHMENTS:
- // Cannot send while there's still attachment(s) being loaded.
- UiUtils.showToastAtBottom(
- R.string.cant_send_message_while_loading_attachments);
- break;
-
- case CheckDraftForSendTask.RESULT_NO_SELF_PHONE_NUMBER_IN_GROUP_MMS:
- mHost.promptForSelfPhoneNumber();
- break;
-
- case CheckDraftForSendTask.RESULT_MESSAGE_OVER_LIMIT:
- Assert.isTrue(checkMessageSize);
- mHost.warnOfExceedingMessageLimit(
- true /*sending*/, false /* tooManyVideos */);
- break;
-
- case CheckDraftForSendTask.RESULT_VIDEO_ATTACHMENT_LIMIT_EXCEEDED:
- Assert.isTrue(checkMessageSize);
- mHost.warnOfExceedingMessageLimit(
- true /*sending*/, true /* tooManyVideos */);
- break;
-
- case CheckDraftForSendTask.RESULT_SIM_NOT_READY:
- // Cannot send if there is no active subscription
- UiUtils.showToastAtBottom(
- R.string.cant_send_message_without_active_subscription);
- break;
-
- default:
- break;
- }
- }
- }, mBinding);
- } else {
- mHost.warnOfMissingActionConditions(true /*sending*/,
- new Runnable() {
- @Override
- public void run() {
- sendMessageInternal(checkMessageSize);
- }
-
- });
- }
- }
-
- public static void playSentSound() {
- // Check if this setting is enabled before playing
- final BuglePrefs prefs = BuglePrefs.getApplicationPrefs();
- final Context context = Factory.get().getApplicationContext();
- final String prefKey = context.getString(R.string.send_sound_pref_key);
- final boolean defaultValue = context.getResources().getBoolean(
- R.bool.send_sound_pref_default);
- if (!prefs.getBoolean(prefKey, defaultValue)) {
- return;
- }
- MediaUtil.get().playSound(context, R.raw.message_sent, null /* completionListener */);
- }
-
- /**
- * {@inheritDoc} from DraftMessageDataListener
- */
- @Override // From DraftMessageDataListener
- public void onDraftChanged(final DraftMessageData data, final int changeFlags) {
- // As this is called asynchronously when message read check bound before updating text
- mBinding.ensureBound(data);
-
- // We have to cache the values of the DraftMessageData because when we set
- // mComposeEditText, its onTextChanged calls updateVisualsOnDraftChanged,
- // which immediately reloads the text from the subject and message fields and replaces
- // what's in the DraftMessageData.
-
- final String subject = data.getMessageSubject();
- final String message = data.getMessageText();
-
- if ((changeFlags & DraftMessageData.MESSAGE_SUBJECT_CHANGED) ==
- DraftMessageData.MESSAGE_SUBJECT_CHANGED) {
- mComposeSubjectText.setText(subject);
-
- // Set the cursor selection to the end since setText resets it to the start
- mComposeSubjectText.setSelection(mComposeSubjectText.getText().length());
- }
-
- if ((changeFlags & DraftMessageData.MESSAGE_TEXT_CHANGED) ==
- DraftMessageData.MESSAGE_TEXT_CHANGED) {
- mComposeEditText.setText(message);
-
- // Set the cursor selection to the end since setText resets it to the start
- mComposeEditText.setSelection(mComposeEditText.getText().length());
- }
-
- if ((changeFlags & DraftMessageData.ATTACHMENTS_CHANGED) ==
- DraftMessageData.ATTACHMENTS_CHANGED) {
- final boolean haveAttachments = mAttachmentPreview.onAttachmentsChanged(data);
- mHost.onAttachmentsChanged(haveAttachments);
- }
-
- if ((changeFlags & DraftMessageData.SELF_CHANGED) == DraftMessageData.SELF_CHANGED) {
- updateOnSelfSubscriptionChange();
- }
- updateVisualsOnDraftChanged();
- }
-
- @Override // From DraftMessageDataListener
- public void onDraftAttachmentLimitReached(final DraftMessageData data) {
- mBinding.ensureBound(data);
- mHost.warnOfExceedingMessageLimit(false /* sending */, false /* tooManyVideos */);
- }
-
- private void updateOnSelfSubscriptionChange() {
- // Refresh the length filters according to the selected self's MmsConfig.
- mComposeEditText.setFilters(new InputFilter[] {
- new LengthFilter(MmsConfig.get(mBinding.getData().getSelfSubId())
- .getMaxTextLimit()) });
- mComposeSubjectText.setFilters(new InputFilter[] {
- new LengthFilter(MmsConfig.get(mBinding.getData().getSelfSubId())
- .getMaxSubjectLength())});
- }
-
- @Override
- public void onMediaItemsSelected(final Collection<MessagePartData> items) {
- mBinding.getData().addAttachments(items);
- announceMediaItemState(true /*isSelected*/);
- }
-
- @Override
- public void onMediaItemsUnselected(final MessagePartData item) {
- mBinding.getData().removeAttachment(item);
- announceMediaItemState(false /*isSelected*/);
- }
-
- @Override
- public void onPendingAttachmentAdded(final PendingAttachmentData pendingItem) {
- mBinding.getData().addPendingAttachment(pendingItem, mBinding);
- resumeComposeMessage();
- }
-
- private void announceMediaItemState(final boolean isSelected) {
- final Resources res = getContext().getResources();
- final String announcement = isSelected ? res.getString(
- R.string.mediapicker_gallery_item_selected_content_description) :
- res.getString(R.string.mediapicker_gallery_item_unselected_content_description);
- AccessibilityUtil.announceForAccessibilityCompat(
- this, null, announcement);
- }
-
- private void announceAttachmentState() {
- if (AccessibilityUtil.isTouchExplorationEnabled(getContext())) {
- int attachmentCount = mBinding.getData().getReadOnlyAttachments().size()
- + mBinding.getData().getReadOnlyPendingAttachments().size();
- final String announcement = getContext().getResources().getQuantityString(
- R.plurals.attachment_changed_accessibility_announcement,
- attachmentCount, attachmentCount);
- AccessibilityUtil.announceForAccessibilityCompat(
- this, null, announcement);
- }
- }
-
- @Override
- public void resumeComposeMessage() {
- mComposeEditText.requestFocus();
- mInputManager.showHideImeKeyboard(true, true);
- announceAttachmentState();
- }
-
- public void clearAttachments() {
- mBinding.getData().clearAttachments(mHost.getAttachmentsClearedFlags());
- mHost.onAttachmentsCleared();
- }
-
- public void requestDraftMessage(boolean clearLocalDraft) {
- mBinding.getData().loadFromStorage(mBinding, null, clearLocalDraft);
- }
-
- public void setDraftMessage(final MessageData message) {
- mBinding.getData().loadFromStorage(mBinding, message, false);
- }
-
- public void writeDraftMessage() {
- final String messageText = mComposeEditText.getText().toString();
- mBinding.getData().setMessageText(messageText);
-
- final String subject = mComposeSubjectText.getText().toString();
- mBinding.getData().setMessageSubject(subject);
-
- mBinding.getData().saveToStorage(mBinding);
- }
-
- private void updateConversationSelfId(final String selfId, final boolean notify) {
- mBinding.getData().setSelfId(selfId, notify);
- }
-
- private Uri getSelfSendButtonIconUri() {
- final Uri overridenSelfUri = mHost.getSelfSendButtonIconUri();
- if (overridenSelfUri != null) {
- return overridenSelfUri;
- }
- final SubscriptionListEntry subscriptionListEntry = getSelfSubscriptionListEntry();
-
- if (subscriptionListEntry != null) {
- return subscriptionListEntry.selectedIconUri;
- }
-
- // Fall back to default self-avatar in the base case.
- final ParticipantData self = mConversationDataModel.getData().getDefaultSelfParticipant();
- return self == null ? null : AvatarUriUtil.createAvatarUri(self);
- }
-
- private SubscriptionListEntry getSelfSubscriptionListEntry() {
- return mConversationDataModel.getData().getSubscriptionEntryForSelfParticipant(
- mBinding.getData().getSelfId(), false /* excludeDefault */);
- }
-
- private boolean isDataLoadedForMessageSend() {
- // Check data loading prerequisites for sending a message.
- return mConversationDataModel != null && mConversationDataModel.isBound() &&
- mConversationDataModel.getData().getParticipantsLoaded();
- }
-
- private void updateVisualsOnDraftChanged() {
- final String messageText = mComposeEditText.getText().toString();
- final DraftMessageData draftMessageData = mBinding.getData();
- draftMessageData.setMessageText(messageText);
-
- final String subject = mComposeSubjectText.getText().toString();
- draftMessageData.setMessageSubject(subject);
- if (!TextUtils.isEmpty(subject)) {
- mSubjectView.setVisibility(View.VISIBLE);
- }
-
- final boolean hasMessageText = (TextUtils.getTrimmedLength(messageText) > 0);
- final boolean hasSubject = (TextUtils.getTrimmedLength(subject) > 0);
- final boolean hasWorkingDraft = hasMessageText || hasSubject ||
- mBinding.getData().hasAttachments();
-
- // Update the SMS text counter.
- final int messageCount = draftMessageData.getNumMessagesToBeSent();
- final int codePointsRemaining = draftMessageData.getCodePointsRemainingInCurrentMessage();
- // Show the counter only if:
- // - We are not in MMS mode
- // - We are going to send more than one message OR we are getting close
- boolean showCounter = false;
- if (!draftMessageData.getIsMms() && (messageCount > 1 ||
- codePointsRemaining <= CODEPOINTS_REMAINING_BEFORE_COUNTER_SHOWN)) {
- showCounter = true;
- }
-
- if (showCounter) {
- // Update the remaining characters and number of messages required.
- final String counterText = messageCount > 1 ? codePointsRemaining + " / " +
- messageCount : String.valueOf(codePointsRemaining);
- mCharCounter.setText(counterText);
- mCharCounter.setVisibility(View.VISIBLE);
- } else {
- mCharCounter.setVisibility(View.INVISIBLE);
- }
-
- // Update the send message button. Self icon uri might be null if self participant data
- // and/or conversation metadata hasn't been loaded by the host.
- final Uri selfSendButtonUri = getSelfSendButtonIconUri();
- int sendWidgetMode = SEND_WIDGET_MODE_SELF_AVATAR;
- if (selfSendButtonUri != null) {
- if (hasWorkingDraft && isDataLoadedForMessageSend()) {
- UiUtils.revealOrHideViewWithAnimation(mSendButton, VISIBLE, null);
- if (isOverriddenAvatarAGroup()) {
- // If the host has overriden the avatar to show a group avatar where the
- // send button sits, we have to hide the group avatar because it can be larger
- // than the send button and pieces of the avatar will stick out from behind
- // the send button.
- UiUtils.revealOrHideViewWithAnimation(mSelfSendIcon, GONE, null);
- }
- mMmsIndicator.setVisibility(draftMessageData.getIsMms() ? VISIBLE : INVISIBLE);
- sendWidgetMode = SEND_WIDGET_MODE_SEND_BUTTON;
- } else {
- mSelfSendIcon.setImageResourceUri(selfSendButtonUri);
- if (isOverriddenAvatarAGroup()) {
- UiUtils.revealOrHideViewWithAnimation(mSelfSendIcon, VISIBLE, null);
- }
- UiUtils.revealOrHideViewWithAnimation(mSendButton, GONE, null);
- mMmsIndicator.setVisibility(INVISIBLE);
- if (shouldShowSimSelector(mConversationDataModel.getData())) {
- sendWidgetMode = SEND_WIDGET_MODE_SIM_SELECTOR;
- }
- }
- } else {
- mSelfSendIcon.setImageResourceUri(null);
- }
-
- if (mSendWidgetMode != sendWidgetMode || sendWidgetMode == SEND_WIDGET_MODE_SIM_SELECTOR) {
- setSendButtonAccessibility(sendWidgetMode);
- mSendWidgetMode = sendWidgetMode;
- }
-
- // Update the text hint on the message box depending on the attachment type.
- final List<MessagePartData> attachments = draftMessageData.getReadOnlyAttachments();
- final int attachmentCount = attachments.size();
- if (attachmentCount == 0) {
- final SubscriptionListEntry subscriptionListEntry =
- mConversationDataModel.getData().getSubscriptionEntryForSelfParticipant(
- mBinding.getData().getSelfId(), false /* excludeDefault */);
- if (subscriptionListEntry == null) {
- mComposeEditText.setHint(R.string.compose_message_view_hint_text);
- } else {
- mComposeEditText.setHint(Html.fromHtml(getResources().getString(
- R.string.compose_message_view_hint_text_multi_sim,
- subscriptionListEntry.displayName)));
- }
- } else {
- int type = -1;
- for (final MessagePartData attachment : attachments) {
- int newType;
- if (attachment.isImage()) {
- newType = ContentType.TYPE_IMAGE;
- } else if (attachment.isAudio()) {
- newType = ContentType.TYPE_AUDIO;
- } else if (attachment.isVideo()) {
- newType = ContentType.TYPE_VIDEO;
- } else if (attachment.isVCard()) {
- newType = ContentType.TYPE_VCARD;
- } else {
- newType = ContentType.TYPE_OTHER;
- }
-
- if (type == -1) {
- type = newType;
- } else if (type != newType || type == ContentType.TYPE_OTHER) {
- type = ContentType.TYPE_OTHER;
- break;
- }
- }
-
- switch (type) {
- case ContentType.TYPE_IMAGE:
- mComposeEditText.setHint(getResources().getQuantityString(
- R.plurals.compose_message_view_hint_text_photo, attachmentCount));
- break;
-
- case ContentType.TYPE_AUDIO:
- mComposeEditText.setHint(getResources().getQuantityString(
- R.plurals.compose_message_view_hint_text_audio, attachmentCount));
- break;
-
- case ContentType.TYPE_VIDEO:
- mComposeEditText.setHint(getResources().getQuantityString(
- R.plurals.compose_message_view_hint_text_video, attachmentCount));
- break;
-
- case ContentType.TYPE_VCARD:
- mComposeEditText.setHint(getResources().getQuantityString(
- R.plurals.compose_message_view_hint_text_vcard, attachmentCount));
- break;
-
- case ContentType.TYPE_OTHER:
- mComposeEditText.setHint(getResources().getQuantityString(
- R.plurals.compose_message_view_hint_text_attachments, attachmentCount));
- break;
-
- default:
- Assert.fail("Unsupported attachment type!");
- break;
- }
- }
- }
-
- private void setSendButtonAccessibility(final int sendWidgetMode) {
- switch (sendWidgetMode) {
- case SEND_WIDGET_MODE_SELF_AVATAR:
- // No send button and no SIM selector; the self send button is no longer
- // important for accessibility.
- mSelfSendIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- mSelfSendIcon.setContentDescription(null);
- mSendButton.setVisibility(View.GONE);
- setSendWidgetAccessibilityTraversalOrder(SEND_WIDGET_MODE_SELF_AVATAR);
- break;
-
- case SEND_WIDGET_MODE_SIM_SELECTOR:
- mSelfSendIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
- mSelfSendIcon.setContentDescription(getSimContentDescription());
- setSendWidgetAccessibilityTraversalOrder(SEND_WIDGET_MODE_SIM_SELECTOR);
- break;
-
- case SEND_WIDGET_MODE_SEND_BUTTON:
- mMmsIndicator.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- mMmsIndicator.setContentDescription(null);
- setSendWidgetAccessibilityTraversalOrder(SEND_WIDGET_MODE_SEND_BUTTON);
- break;
- }
- }
-
- private String getSimContentDescription() {
- final SubscriptionListEntry sub = getSelfSubscriptionListEntry();
- if (sub != null) {
- return getResources().getString(
- R.string.sim_selector_button_content_description_with_selection,
- sub.displayName);
- } else {
- return getResources().getString(
- R.string.sim_selector_button_content_description);
- }
- }
-
- // Set accessibility traversal order of the components in the send widget.
- private void setSendWidgetAccessibilityTraversalOrder(final int mode) {
- if (OsUtil.isAtLeastL_MR1()) {
- mAttachMediaButton.setAccessibilityTraversalBefore(R.id.compose_message_text);
- switch (mode) {
- case SEND_WIDGET_MODE_SIM_SELECTOR:
- mComposeEditText.setAccessibilityTraversalBefore(R.id.self_send_icon);
- break;
- case SEND_WIDGET_MODE_SEND_BUTTON:
- mComposeEditText.setAccessibilityTraversalBefore(R.id.send_message_button);
- break;
- default:
- break;
- }
- }
- }
-
- @Override
- public void afterTextChanged(final Editable editable) {
- }
-
- @Override
- public void beforeTextChanged(final CharSequence s, final int start, final int count,
- final int after) {
- if (mHost.shouldHideAttachmentsWhenSimSelectorShown()) {
- hideSimSelector();
- }
- }
-
- private void hideSimSelector() {
- if (mInputManager.showHideSimSelector(false /* show */, true /* animate */)) {
- // Now that the sim selector has been hidden, reshow the attachments if they
- // have been hidden.
- hideAttachmentsWhenShowingSims(false /*simPickerVisible*/);
- }
- }
-
- @Override
- public void onTextChanged(final CharSequence s, final int start, final int before,
- final int count) {
- final BugleActionBarActivity activity = (mOriginalContext instanceof BugleActionBarActivity)
- ? (BugleActionBarActivity) mOriginalContext : null;
- if (activity != null && activity.getIsDestroyed()) {
- LogUtil.v(LogUtil.BUGLE_TAG, "got onTextChanged after onDestroy");
-
- // if we get onTextChanged after the activity is destroyed then, ah, wtf
- // b/18176615
- // This appears to have occurred as the result of orientation change.
- return;
- }
-
- mBinding.ensureBound();
- updateVisualsOnDraftChanged();
- }
-
- @Override
- public PlainTextEditText getComposeEditText() {
- return mComposeEditText;
- }
-
- public void displayPhoto(final Uri photoUri, final Rect imageBounds) {
- mHost.displayPhoto(photoUri, imageBounds, true /* isDraft */);
- }
-
- public void updateConversationSelfIdOnExternalChange(final String selfId) {
- updateConversationSelfId(selfId, true /* notify */);
- }
-
- /**
- * The selfId of the conversation. As soon as the DraftMessageData successfully loads (i.e.
- * getSelfId() is non-null), the selfId in DraftMessageData is treated as the sole source
- * of truth for conversation self id since it reflects any pending self id change the user
- * makes in the UI.
- */
- public String getConversationSelfId() {
- return mBinding.getData().getSelfId();
- }
-
- public void selectSim(SubscriptionListEntry subscriptionData) {
- final String oldSelfId = getConversationSelfId();
- final String newSelfId = subscriptionData.selfParticipantId;
- Assert.notNull(newSelfId);
- // Don't attempt to change self if self hasn't been loaded, or if self hasn't changed.
- if (oldSelfId == null || TextUtils.equals(oldSelfId, newSelfId)) {
- return;
- }
- updateConversationSelfId(newSelfId, true /* notify */);
- }
-
- public void hideAllComposeInputs(final boolean animate) {
- mInputManager.hideAllInputs(animate);
- }
-
- public void saveInputState(final Bundle outState) {
- mInputManager.onSaveInputState(outState);
- }
-
- public void resetMediaPickerState() {
- mInputManager.resetMediaPickerState();
- }
-
- public boolean onBackPressed() {
- return mInputManager.onBackPressed();
- }
-
- public boolean onNavigationUpPressed() {
- return mInputManager.onNavigationUpPressed();
- }
-
- public boolean updateActionBar(final ActionBar actionBar) {
- return mInputManager != null ? mInputManager.updateActionBar(actionBar) : false;
- }
-
- public static boolean shouldShowSimSelector(final ConversationData convData) {
- return OsUtil.isAtLeastL_MR1() &&
- convData.getSelfParticipantsCountExcludingDefault(true /* activeOnly */) > 1;
- }
-
- public void sendMessageIgnoreMessageSizeLimit() {
- sendMessageInternal(false /* checkMessageSize */);
- }
-
- public void onAttachmentPreviewLongClicked() {
- mHost.showAttachmentChooser();
- }
-
- @Override
- public void onDraftAttachmentLoadFailed() {
- mHost.notifyOfAttachmentLoadFailed();
- }
-
- private boolean isOverriddenAvatarAGroup() {
- final Uri overridenSelfUri = mHost.getSelfSendButtonIconUri();
- if (overridenSelfUri == null) {
- return false;
- }
- return AvatarUriUtil.TYPE_GROUP_URI.equals(AvatarUriUtil.getAvatarType(overridenSelfUri));
- }
-
- @Override
- public void setAccessibility(boolean enabled) {
- if (enabled) {
- mAttachMediaButton.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
- mComposeEditText.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
- mSendButton.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
- setSendButtonAccessibility(mSendWidgetMode);
- } else {
- mSelfSendIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- mComposeEditText.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- mSendButton.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- mAttachMediaButton.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationActivity.java b/src/com/android/messaging/ui/conversation/ConversationActivity.java
deleted file mode 100644
index 66310ea..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationActivity.java
+++ /dev/null
@@ -1,379 +0,0 @@
-/*
- * 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 android.support.v7.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();
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationActivityUiState.java b/src/com/android/messaging/ui/conversation/ConversationActivityUiState.java
deleted file mode 100644
index 1469c93..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationActivityUiState.java
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * 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.os.Parcel;
-import android.os.Parcelable;
-
-import com.android.messaging.ui.contact.ContactPickerFragment;
-import com.android.messaging.util.Assert;
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * Keeps track of the different UI states that the ConversationActivity may be in. This acts as
- * a state machine which, based on different actions (e.g. onAddMoreParticipants), notifies the
- * ConversationActivity about any state UI change so it can update the visuals. This class
- * implements Parcelable and it's persisted across activity tear down and relaunch.
- */
-public class ConversationActivityUiState implements Parcelable, Cloneable {
- interface ConversationActivityUiStateHost {
- void onConversationContactPickerUiStateChanged(int oldState, int newState, boolean animate);
- }
-
- /*------ Overall UI states (conversation & contact picker) ------*/
-
- /** Only a full screen conversation is showing. */
- public static final int STATE_CONVERSATION_ONLY = 1;
- /** Only a full screen contact picker is showing asking user to pick the initial contact. */
- public static final int STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT = 2;
- /**
- * Only a full screen contact picker is showing asking user to pick more participants. This
- * happens after the user picked the initial contact, and then decide to go back and add more.
- */
- public static final int STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS = 3;
- /**
- * Only a full screen contact picker is showing asking user to pick more participants. However
- * user has reached max number of conversation participants and can add no more.
- */
- public static final int STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS = 4;
- /**
- * A hybrid mode where the conversation view + contact chips view are showing. This happens
- * right after the user picked the initial contact for which a 1-1 conversation is fetched or
- * created.
- */
- public static final int STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW = 5;
-
- // The overall UI state of the ConversationActivity.
- private int mConversationContactUiState;
-
- // The currently displayed conversation (if any).
- private String mConversationId;
-
- // Indicates whether we should put focus in the compose message view when the
- // ConversationFragment is attached. This is a transient state that's not persisted as
- // part of the parcelable.
- private boolean mPendingResumeComposeMessage = false;
-
- // The owner ConversationActivity. This is not parceled since the instance always change upon
- // object reuse.
- private ConversationActivityUiStateHost mHost;
-
- // Indicates the owning ConverastionActivity is in the process of updating its UI presentation
- // to be in sync with the UI states. Outside of the UI updates, the UI states here should
- // ALWAYS be consistent with the actual states of the activity.
- private int mUiUpdateCount;
-
- /**
- * Create a new instance with an initial conversation id.
- */
- ConversationActivityUiState(final String conversationId) {
- // The conversation activity may be initialized with only one of two states:
- // Conversation-only (when there's a conversation id) or picking initial contact
- // (when no conversation id is given).
- mConversationId = conversationId;
- mConversationContactUiState = conversationId == null ?
- STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT : STATE_CONVERSATION_ONLY;
- }
-
- public void setHost(final ConversationActivityUiStateHost host) {
- mHost = host;
- }
-
- public boolean shouldShowConversationFragment() {
- return mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW ||
- mConversationContactUiState == STATE_CONVERSATION_ONLY;
- }
-
- public boolean shouldShowContactPickerFragment() {
- return mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS ||
- mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS ||
- mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT ||
- mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW;
- }
-
- /**
- * Returns whether there's a pending request to resume message compose (i.e. set focus to
- * the compose message view and show the soft keyboard). If so, this request will be served
- * when the conversation fragment get created and resumed. This happens when the user commits
- * participant selection for a group conversation and goes back to the conversation fragment.
- * Since conversation fragment creation happens asynchronously, we issue and track this
- * pending request for it to be eventually fulfilled.
- */
- public boolean shouldResumeComposeMessage() {
- if (mPendingResumeComposeMessage) {
- // This is a one-shot operation that just keeps track of the pending resume compose
- // state. This is also a non-critical operation so we don't care about failure case.
- mPendingResumeComposeMessage = false;
- return true;
- }
- return false;
- }
-
- public int getDesiredContactPickingMode() {
- switch (mConversationContactUiState) {
- case STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS:
- return ContactPickerFragment.MODE_PICK_MORE_CONTACTS;
- case STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS:
- return ContactPickerFragment.MODE_PICK_MAX_PARTICIPANTS;
- case STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT:
- return ContactPickerFragment.MODE_PICK_INITIAL_CONTACT;
- case STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW:
- return ContactPickerFragment.MODE_CHIPS_ONLY;
- default:
- Assert.fail("Invalid contact picking mode for ConversationActivity!");
- return ContactPickerFragment.MODE_UNDEFINED;
- }
- }
-
- public String getConversationId() {
- return mConversationId;
- }
-
- /**
- * Called whenever the contact picker fragment successfully fetched or created a conversation.
- */
- public void onGetOrCreateConversation(final String conversationId) {
- int newState = STATE_CONVERSATION_ONLY;
- if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT) {
- newState = STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW;
- } else if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS ||
- mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS) {
- newState = STATE_CONVERSATION_ONLY;
- } else {
- // New conversation should only be created when we are in one of the contact picking
- // modes.
- Assert.fail("Invalid conversation activity state: can't create conversation!");
- }
- mConversationId = conversationId;
- performUiStateUpdate(newState, true);
- }
-
- /**
- * Called when the user started composing message. If we are in the hybrid chips state, we
- * should commit to enter the conversation only state.
- */
- public void onStartMessageCompose() {
- // This cannot happen when we are in one of the full-screen contact picking states.
- Assert.isTrue(mConversationContactUiState != STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT &&
- mConversationContactUiState != STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS &&
- mConversationContactUiState != STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS);
- if (mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW) {
- performUiStateUpdate(STATE_CONVERSATION_ONLY, true);
- }
- }
-
- /**
- * Called when the user initiated an action to add more participants in the hybrid state,
- * namely clicking on the "add more participants" button or entered a new contact chip via
- * auto-complete.
- */
- public void onAddMoreParticipants() {
- if (mConversationContactUiState == STATE_HYBRID_WITH_CONVERSATION_AND_CHIPS_VIEW) {
- mPendingResumeComposeMessage = true;
- performUiStateUpdate(STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS, true);
- } else {
- // This is only possible in the hybrid state.
- Assert.fail("Invalid conversation activity state: can't add more participants!");
- }
- }
-
- /**
- * Called each time the number of participants is updated to check against the limit and
- * update the ui state accordingly.
- */
- public void onParticipantCountUpdated(final boolean canAddMoreParticipants) {
- if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS
- && !canAddMoreParticipants) {
- performUiStateUpdate(STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS, false);
- } else if (mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_MAX_PARTICIPANTS
- && canAddMoreParticipants) {
- performUiStateUpdate(STATE_CONTACT_PICKER_ONLY_ADD_MORE_CONTACTS, false);
- }
- }
-
- private void performUiStateUpdate(final int conversationContactState, final boolean animate) {
- // This starts one UI update cycle, during which we allow the conversation activity's
- // UI presentation to be temporarily out of sync with the states here.
- beginUiUpdate();
-
- if (conversationContactState != mConversationContactUiState) {
- final int oldState = mConversationContactUiState;
- mConversationContactUiState = conversationContactState;
- notifyOnOverallUiStateChanged(oldState, mConversationContactUiState, animate);
- }
- endUiUpdate();
- }
-
- private void notifyOnOverallUiStateChanged(
- final int oldState, final int newState, final boolean animate) {
- // Always verify state validity whenever we have a state change.
- assertValidState();
- Assert.isTrue(isUiUpdateInProgress());
-
- // Only do this if we are still attached to the host. mHost can be null if the host
- // activity is already destroyed, but due to timing the contained UI components may still
- // receive events such as focus change and trigger a callback to the Ui state. We'd like
- // to guard against those cases.
- if (mHost != null) {
- mHost.onConversationContactPickerUiStateChanged(oldState, newState, animate);
- }
- }
-
- private void assertValidState() {
- // Conversation id may be null IF AND ONLY IF the user is picking the initial contact to
- // start a conversation.
- Assert.isTrue((mConversationContactUiState == STATE_CONTACT_PICKER_ONLY_INITIAL_CONTACT) ==
- (mConversationId == null));
- }
-
- private void beginUiUpdate() {
- mUiUpdateCount++;
- }
-
- private void endUiUpdate() {
- if (--mUiUpdateCount < 0) {
- Assert.fail("Unbalanced Ui updates!");
- }
- }
-
- private boolean isUiUpdateInProgress() {
- return mUiUpdateCount > 0;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(final Parcel dest, final int flags) {
- dest.writeInt(mConversationContactUiState);
- dest.writeString(mConversationId);
- }
-
- private ConversationActivityUiState(final Parcel in) {
- mConversationContactUiState = in.readInt();
- mConversationId = in.readString();
-
- // Always verify state validity whenever we initialize states.
- assertValidState();
- }
-
- public static final Parcelable.Creator<ConversationActivityUiState> CREATOR
- = new Parcelable.Creator<ConversationActivityUiState>() {
- @Override
- public ConversationActivityUiState createFromParcel(final Parcel in) {
- return new ConversationActivityUiState(in);
- }
-
- @Override
- public ConversationActivityUiState[] newArray(final int size) {
- return new ConversationActivityUiState[size];
- }
- };
-
- @Override
- protected ConversationActivityUiState clone() {
- try {
- return (ConversationActivityUiState) super.clone();
- } catch (CloneNotSupportedException e) {
- Assert.fail("ConversationActivityUiState: failed to clone(). Is there a mutable " +
- "reference?");
- }
- return null;
- }
-
- /**
- * allows for overridding the internal UI state. Should never be called except by test code.
- */
- @VisibleForTesting
- void testSetUiState(final int uiState) {
- mConversationContactUiState = uiState;
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationFastScroller.java b/src/com/android/messaging/ui/conversation/ConversationFastScroller.java
deleted file mode 100644
index b15f05a..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationFastScroller.java
+++ /dev/null
@@ -1,489 +0,0 @@
-/*
- * 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.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.graphics.drawable.StateListDrawable;
-import android.os.Handler;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.AdapterDataObserver;
-import android.support.v7.widget.RecyclerView.ViewHolder;
-import android.util.StateSet;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.View.OnLayoutChangeListener;
-import android.view.ViewGroupOverlay;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ConversationMessageData;
-import com.android.messaging.ui.ConversationDrawables;
-import com.android.messaging.util.Dates;
-import com.android.messaging.util.OsUtil;
-
-/**
- * Adds a "fast-scroll" bar to the conversation RecyclerView that shows the current position within
- * the conversation and allows quickly moving to another position by dragging the scrollbar thumb
- * up or down. As the thumb is dragged, we show a floating bubble alongside it that shows the
- * date/time of the first visible message at the current position.
- */
-public class ConversationFastScroller extends RecyclerView.OnScrollListener implements
- OnLayoutChangeListener, RecyclerView.OnItemTouchListener {
-
- /**
- * Creates a {@link ConversationFastScroller} instance, attached to the provided
- * {@link RecyclerView}.
- *
- * @param rv the conversation RecyclerView
- * @param position where the scrollbar should appear (either {@code POSITION_RIGHT_SIDE} or
- * {@code POSITION_LEFT_SIDE})
- * @return a new ConversationFastScroller, or {@code null} if fast-scrolling is not supported
- * (the feature requires Jellybean MR2 or newer)
- */
- public static ConversationFastScroller addTo(RecyclerView rv, int position) {
- if (OsUtil.isAtLeastJB_MR2()) {
- return new ConversationFastScroller(rv, position);
- }
- return null;
- }
-
- public static final int POSITION_RIGHT_SIDE = 0;
- public static final int POSITION_LEFT_SIDE = 1;
-
- private static final int MIN_PAGES_TO_ENABLE = 7;
- private static final int SHOW_ANIMATION_DURATION_MS = 150;
- private static final int HIDE_ANIMATION_DURATION_MS = 300;
- private static final int HIDE_DELAY_MS = 1500;
-
- private final Context mContext;
- private final RecyclerView mRv;
- private final ViewGroupOverlay mOverlay;
- private final ImageView mTrackImageView;
- private final ImageView mThumbImageView;
- private final TextView mPreviewTextView;
-
- private final int mTrackWidth;
- private final int mThumbHeight;
- private final int mPreviewHeight;
- private final int mPreviewMinWidth;
- private final int mPreviewMarginTop;
- private final int mPreviewMarginLeftRight;
- private final int mTouchSlop;
-
- private final Rect mContainer = new Rect();
- private final Handler mHandler = new Handler();
-
- // Whether to render the scrollbar on the right side (otherwise it'll be on the left).
- private final boolean mPosRight;
-
- // Whether the scrollbar is currently visible (it may still be animating).
- private boolean mVisible = false;
-
- // Whether we are waiting to hide the scrollbar (i.e. scrolling has stopped).
- private boolean mPendingHide = false;
-
- // Whether the user is currently dragging the thumb up or down.
- private boolean mDragging = false;
-
- // Animations responsible for hiding the scrollbar & preview. May be null.
- private AnimatorSet mHideAnimation;
- private ObjectAnimator mHidePreviewAnimation;
-
- private final Runnable mHideTrackRunnable = new Runnable() {
- @Override
- public void run() {
- hide(true /* animate */);
- mPendingHide = false;
- }
- };
-
- private ConversationFastScroller(RecyclerView rv, int position) {
- mContext = rv.getContext();
- mRv = rv;
- mRv.addOnLayoutChangeListener(this);
- mRv.addOnScrollListener(this);
- mRv.addOnItemTouchListener(this);
- mRv.getAdapter().registerAdapterDataObserver(new AdapterDataObserver() {
- @Override
- public void onChanged() {
- updateScrollPos();
- }
- });
- mPosRight = (position == POSITION_RIGHT_SIDE);
-
- // Cache the dimensions we'll need during layout
- final Resources res = mContext.getResources();
- mTrackWidth = res.getDimensionPixelSize(R.dimen.fastscroll_track_width);
- mThumbHeight = res.getDimensionPixelSize(R.dimen.fastscroll_thumb_height);
- mPreviewHeight = res.getDimensionPixelSize(R.dimen.fastscroll_preview_height);
- mPreviewMinWidth = res.getDimensionPixelSize(R.dimen.fastscroll_preview_min_width);
- mPreviewMarginTop = res.getDimensionPixelOffset(R.dimen.fastscroll_preview_margin_top);
- mPreviewMarginLeftRight = res.getDimensionPixelOffset(
- R.dimen.fastscroll_preview_margin_left_right);
- mTouchSlop = res.getDimensionPixelOffset(R.dimen.fastscroll_touch_slop);
-
- final LayoutInflater inflator = LayoutInflater.from(mContext);
- mTrackImageView = (ImageView) inflator.inflate(R.layout.fastscroll_track, null);
- mThumbImageView = (ImageView) inflator.inflate(R.layout.fastscroll_thumb, null);
- mPreviewTextView = (TextView) inflator.inflate(R.layout.fastscroll_preview, null);
-
- refreshConversationThemeColor();
-
- // Add the fast scroll views to the overlay, so they are rendered above the list
- mOverlay = rv.getOverlay();
- mOverlay.add(mTrackImageView);
- mOverlay.add(mThumbImageView);
- mOverlay.add(mPreviewTextView);
-
- hide(false /* animate */);
- mPreviewTextView.setAlpha(0f);
- }
-
- public void refreshConversationThemeColor() {
- mPreviewTextView.setBackground(
- ConversationDrawables.get().getFastScrollPreviewDrawable(mPosRight));
- if (OsUtil.isAtLeastL()) {
- final StateListDrawable drawable = new StateListDrawable();
- drawable.addState(new int[]{ android.R.attr.state_pressed },
- ConversationDrawables.get().getFastScrollThumbDrawable(true /* pressed */));
- drawable.addState(StateSet.WILD_CARD,
- ConversationDrawables.get().getFastScrollThumbDrawable(false /* pressed */));
- mThumbImageView.setImageDrawable(drawable);
- } else {
- // Android pre-L doesn't seem to handle a StateListDrawable containing a tinted
- // drawable (it's rendered in the filter base color, which is red), so fall back to
- // just the regular (non-pressed) drawable.
- mThumbImageView.setImageDrawable(
- ConversationDrawables.get().getFastScrollThumbDrawable(false /* pressed */));
- }
- }
-
- @Override
- public void onScrollStateChanged(final RecyclerView view, final int newState) {
- if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
- // Only show the scrollbar once the user starts scrolling
- if (!mVisible && isEnabled()) {
- show();
- }
- cancelAnyPendingHide();
- } else if (newState == RecyclerView.SCROLL_STATE_IDLE && !mDragging) {
- // Hide the scrollbar again after scrolling stops
- hideAfterDelay();
- }
- }
-
- private boolean isEnabled() {
- final int range = mRv.computeVerticalScrollRange();
- final int extent = mRv.computeVerticalScrollExtent();
-
- if (range == 0 || extent == 0) {
- return false; // Conversation isn't long enough to scroll
- }
- // Only enable scrollbars for conversations long enough that they would require several
- // flings to scroll through.
- final float pages = (float) range / extent;
- return (pages > MIN_PAGES_TO_ENABLE);
- }
-
- private void show() {
- if (mHideAnimation != null && mHideAnimation.isRunning()) {
- mHideAnimation.cancel();
- }
- // Slide the scrollbar in from the side
- ObjectAnimator trackSlide = ObjectAnimator.ofFloat(mTrackImageView, View.TRANSLATION_X, 0);
- ObjectAnimator thumbSlide = ObjectAnimator.ofFloat(mThumbImageView, View.TRANSLATION_X, 0);
- AnimatorSet animation = new AnimatorSet();
- animation.playTogether(trackSlide, thumbSlide);
- animation.setDuration(SHOW_ANIMATION_DURATION_MS);
- animation.start();
-
- mVisible = true;
- updateScrollPos();
- }
-
- private void hideAfterDelay() {
- cancelAnyPendingHide();
- mHandler.postDelayed(mHideTrackRunnable, HIDE_DELAY_MS);
- mPendingHide = true;
- }
-
- private void cancelAnyPendingHide() {
- if (mPendingHide) {
- mHandler.removeCallbacks(mHideTrackRunnable);
- }
- }
-
- private void hide(boolean animate) {
- final int hiddenTranslationX = mPosRight ? mTrackWidth : -mTrackWidth;
- if (animate) {
- // Slide the scrollbar off to the side
- ObjectAnimator trackSlide = ObjectAnimator.ofFloat(mTrackImageView, View.TRANSLATION_X,
- hiddenTranslationX);
- ObjectAnimator thumbSlide = ObjectAnimator.ofFloat(mThumbImageView, View.TRANSLATION_X,
- hiddenTranslationX);
- mHideAnimation = new AnimatorSet();
- mHideAnimation.playTogether(trackSlide, thumbSlide);
- mHideAnimation.setDuration(HIDE_ANIMATION_DURATION_MS);
- mHideAnimation.start();
- } else {
- mTrackImageView.setTranslationX(hiddenTranslationX);
- mThumbImageView.setTranslationX(hiddenTranslationX);
- }
-
- mVisible = false;
- }
-
- private void showPreview() {
- if (mHidePreviewAnimation != null && mHidePreviewAnimation.isRunning()) {
- mHidePreviewAnimation.cancel();
- }
- mPreviewTextView.setAlpha(1f);
- }
-
- private void hidePreview() {
- mHidePreviewAnimation = ObjectAnimator.ofFloat(mPreviewTextView, View.ALPHA, 0f);
- mHidePreviewAnimation.setDuration(HIDE_ANIMATION_DURATION_MS);
- mHidePreviewAnimation.start();
- }
-
- @Override
- public void onScrolled(final RecyclerView view, final int dx, final int dy) {
- updateScrollPos();
- }
-
- private void updateScrollPos() {
- if (!mVisible) {
- return;
- }
- final int verticalScrollLength = mContainer.height() - mThumbHeight;
- final int verticalScrollStart = mContainer.top + mThumbHeight / 2;
-
- final float scrollRatio = computeScrollRatio();
- final int thumbCenterY = verticalScrollStart + (int)(verticalScrollLength * scrollRatio);
- layoutThumb(thumbCenterY);
-
- if (mDragging) {
- updatePreviewText();
- layoutPreview(thumbCenterY);
- }
- }
-
- /**
- * Returns the current position in the conversation, as a value between 0 and 1, inclusive.
- * The top of the conversation is 0, the bottom is 1, the exact middle is 0.5, and so on.
- */
- private float computeScrollRatio() {
- final int range = mRv.computeVerticalScrollRange();
- final int extent = mRv.computeVerticalScrollExtent();
- int offset = mRv.computeVerticalScrollOffset();
-
- if (range == 0 || extent == 0) {
- // If the conversation doesn't scroll, we're at the bottom.
- return 1.0f;
- }
- final int scrollRange = range - extent;
- offset = Math.min(offset, scrollRange);
- return offset / (float) scrollRange;
- }
-
- private void updatePreviewText() {
- final LinearLayoutManager lm = (LinearLayoutManager) mRv.getLayoutManager();
- final int pos = lm.findFirstVisibleItemPosition();
- if (pos == RecyclerView.NO_POSITION) {
- return;
- }
- final ViewHolder vh = mRv.findViewHolderForAdapterPosition(pos);
- if (vh == null) {
- // This can happen if the messages update while we're dragging the thumb.
- return;
- }
- final ConversationMessageView messageView = (ConversationMessageView) vh.itemView;
- final ConversationMessageData messageData = messageView.getData();
- final long timestamp = messageData.getReceivedTimeStamp();
- final CharSequence timestampText = Dates.getFastScrollPreviewTimeString(timestamp);
- mPreviewTextView.setText(timestampText);
- }
-
- @Override
- public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
- if (!mVisible) {
- return false;
- }
- // If the user presses down on the scroll thumb, we'll start intercepting events from the
- // RecyclerView so we can handle the move events while they're dragging it up/down.
- final int action = e.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- if (isInsideThumb(e.getX(), e.getY())) {
- startDrag();
- return true;
- }
- break;
- case MotionEvent.ACTION_MOVE:
- if (mDragging) {
- return true;
- }
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- if (mDragging) {
- cancelDrag();
- }
- return false;
- }
- return false;
- }
-
- private boolean isInsideThumb(float x, float y) {
- final int hitTargetLeft = mThumbImageView.getLeft() - mTouchSlop;
- final int hitTargetRight = mThumbImageView.getRight() + mTouchSlop;
-
- if (x < hitTargetLeft || x > hitTargetRight) {
- return false;
- }
- if (y < mThumbImageView.getTop() || y > mThumbImageView.getBottom()) {
- return false;
- }
- return true;
- }
-
- @Override
- public void onTouchEvent(RecyclerView rv, MotionEvent e) {
- if (!mDragging) {
- return;
- }
- final int action = e.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_MOVE:
- handleDragMove(e.getY());
- break;
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- cancelDrag();
- break;
- }
- }
-
- @Override
- public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
- }
-
- private void startDrag() {
- mDragging = true;
- mThumbImageView.setPressed(true);
- updateScrollPos();
- showPreview();
- cancelAnyPendingHide();
- }
-
- private void handleDragMove(float y) {
- final int verticalScrollLength = mContainer.height() - mThumbHeight;
- final int verticalScrollStart = mContainer.top + (mThumbHeight / 2);
-
- // Convert the desired position from px to a scroll position in the conversation.
- float dragScrollRatio = (y - verticalScrollStart) / verticalScrollLength;
- dragScrollRatio = Math.max(dragScrollRatio, 0.0f);
- dragScrollRatio = Math.min(dragScrollRatio, 1.0f);
-
- // Scroll the RecyclerView to a new position.
- final int itemCount = mRv.getAdapter().getItemCount();
- final int itemPos = (int)((itemCount - 1) * dragScrollRatio);
- mRv.scrollToPosition(itemPos);
- }
-
- private void cancelDrag() {
- mDragging = false;
- mThumbImageView.setPressed(false);
- hidePreview();
- hideAfterDelay();
- }
-
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- if (!mVisible) {
- hide(false /* animate */);
- }
- // The container is the size of the RecyclerView that's visible on screen. We have to
- // exclude the top padding, because it's usually hidden behind the conversation action bar.
- mContainer.set(left, top + mRv.getPaddingTop(), right, bottom);
- layoutTrack();
- updateScrollPos();
- }
-
- private void layoutTrack() {
- int trackHeight = Math.max(0, mContainer.height());
- int widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTrackWidth, MeasureSpec.EXACTLY);
- int heightMeasureSpec = MeasureSpec.makeMeasureSpec(trackHeight, MeasureSpec.EXACTLY);
- mTrackImageView.measure(widthMeasureSpec, heightMeasureSpec);
-
- int left = mPosRight ? (mContainer.right - mTrackWidth) : mContainer.left;
- int top = mContainer.top;
- int right = mPosRight ? mContainer.right : (mContainer.left + mTrackWidth);
- int bottom = mContainer.bottom;
- mTrackImageView.layout(left, top, right, bottom);
- }
-
- private void layoutThumb(int centerY) {
- int widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTrackWidth, MeasureSpec.EXACTLY);
- int heightMeasureSpec = MeasureSpec.makeMeasureSpec(mThumbHeight, MeasureSpec.EXACTLY);
- mThumbImageView.measure(widthMeasureSpec, heightMeasureSpec);
-
- int left = mPosRight ? (mContainer.right - mTrackWidth) : mContainer.left;
- int top = centerY - (mThumbImageView.getHeight() / 2);
- int right = mPosRight ? mContainer.right : (mContainer.left + mTrackWidth);
- int bottom = top + mThumbHeight;
- mThumbImageView.layout(left, top, right, bottom);
- }
-
- private void layoutPreview(int centerY) {
- int widthMeasureSpec = MeasureSpec.makeMeasureSpec(mContainer.width(), MeasureSpec.AT_MOST);
- int heightMeasureSpec = MeasureSpec.makeMeasureSpec(mPreviewHeight, MeasureSpec.EXACTLY);
- mPreviewTextView.measure(widthMeasureSpec, heightMeasureSpec);
-
- // Ensure that the preview bubble is at least as wide as it is tall
- if (mPreviewTextView.getMeasuredWidth() < mPreviewMinWidth) {
- widthMeasureSpec = MeasureSpec.makeMeasureSpec(mPreviewMinWidth, MeasureSpec.EXACTLY);
- mPreviewTextView.measure(widthMeasureSpec, heightMeasureSpec);
- }
- final int previewMinY = mContainer.top + mPreviewMarginTop;
-
- final int left, right;
- if (mPosRight) {
- right = mContainer.right - mTrackWidth - mPreviewMarginLeftRight;
- left = right - mPreviewTextView.getMeasuredWidth();
- } else {
- left = mContainer.left + mTrackWidth + mPreviewMarginLeftRight;
- right = left + mPreviewTextView.getMeasuredWidth();
- }
-
- int bottom = centerY;
- int top = bottom - mPreviewTextView.getMeasuredHeight();
- if (top < previewMinY) {
- top = previewMinY;
- bottom = top + mPreviewTextView.getMeasuredHeight();
- }
- mPreviewTextView.layout(left, top, right, bottom);
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationFragment.java b/src/com/android/messaging/ui/conversation/ConversationFragment.java
deleted file mode 100644
index a6a191a..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationFragment.java
+++ /dev/null
@@ -1,1662 +0,0 @@
-/*
- * 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.Manifest;
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.DownloadManager;
-import android.app.Fragment;
-import android.app.FragmentManager;
-import android.app.FragmentTransaction;
-import android.content.BroadcastReceiver;
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
-import android.content.DialogInterface.OnClickListener;
-import android.content.DialogInterface.OnDismissListener;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Configuration;
-import android.database.Cursor;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.ColorDrawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Parcelable;
-import android.support.v4.content.LocalBroadcastManager;
-import android.support.v4.text.BidiFormatter;
-import android.support.v4.text.TextDirectionHeuristicsCompat;
-import android.support.v7.app.ActionBar;
-import android.support.v7.widget.DefaultItemAnimator;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.ViewHolder;
-import android.text.TextUtils;
-import android.view.ActionMode;
-import android.view.Display;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.action.InsertNewMessageAction;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.binding.ImmutableBindingRef;
-import com.android.messaging.datamodel.data.ConversationData;
-import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener;
-import com.android.messaging.datamodel.data.ConversationMessageData;
-import com.android.messaging.datamodel.data.ConversationParticipantsData;
-import com.android.messaging.datamodel.data.DraftMessageData;
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
-import com.android.messaging.ui.AttachmentPreview;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.ConversationDrawables;
-import com.android.messaging.ui.SnackBar;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.animation.PopupTransitionAnimation;
-import com.android.messaging.ui.contact.AddContactsConfirmationDialog;
-import com.android.messaging.ui.conversation.ComposeMessageView.IComposeMessageViewHost;
-import com.android.messaging.ui.conversation.ConversationInputManager.ConversationInputHost;
-import com.android.messaging.ui.conversation.ConversationMessageView.ConversationMessageViewHost;
-import com.android.messaging.ui.mediapicker.MediaPicker;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.ChangeDefaultSmsAppHelper;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.ImeUtil;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.SafeAsyncTask;
-import com.android.messaging.util.TextUtil;
-import com.android.messaging.util.UiUtils;
-import com.android.messaging.util.UriUtil;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Shows a list of messages/parts comprising a conversation.
- */
-public class ConversationFragment extends Fragment implements ConversationDataListener,
- IComposeMessageViewHost, ConversationMessageViewHost, ConversationInputHost,
- DraftMessageDataListener {
-
- public interface ConversationFragmentHost extends ImeUtil.ImeStateHost {
- void onStartComposeMessage();
- void onConversationMetadataUpdated();
- boolean shouldResumeComposeMessage();
- void onFinishCurrentConversation();
- void invalidateActionBar();
- ActionMode startActionMode(ActionMode.Callback callback);
- void dismissActionMode();
- ActionMode getActionMode();
- void onConversationMessagesUpdated(int numberOfMessages);
- void onConversationParticipantDataLoaded(int numberOfParticipants);
- boolean isActiveAndFocused();
- }
-
- public static final String FRAGMENT_TAG = "conversation";
-
- static final int REQUEST_CHOOSE_ATTACHMENTS = 2;
- private static final int JUMP_SCROLL_THRESHOLD = 15;
- // We animate the message from draft to message list, if we the message doesn't show up in the
- // list within this time limit, then we just do a fade in animation instead
- public static final int MESSAGE_ANIMATION_MAX_WAIT = 500;
-
- private ComposeMessageView mComposeMessageView;
- private RecyclerView mRecyclerView;
- private ConversationMessageAdapter mAdapter;
- private ConversationFastScroller mFastScroller;
-
- private View mConversationComposeDivider;
- private ChangeDefaultSmsAppHelper mChangeDefaultSmsAppHelper;
-
- private String mConversationId;
- // If the fragment receives a draft as part of the invocation this is set
- private MessageData mIncomingDraft;
-
- // This binding keeps track of our associated ConversationData instance
- // A binding should have the lifetime of the owning component,
- // don't recreate, unbind and bind if you need new data
- @VisibleForTesting
- final Binding<ConversationData> mBinding = BindingBase.createBinding(this);
-
- // Saved Instance State Data - only for temporal data which is nice to maintain but not
- // critical for correctness.
- private static final String SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY = "conversationViewState";
- private Parcelable mListState;
-
- private ConversationFragmentHost mHost;
-
- protected List<Integer> mFilterResults;
-
- // The minimum scrolling distance between RecyclerView's scroll change event beyong which
- // a fling motion is considered fast, in which case we'll delay load image attachments for
- // perf optimization.
- private int mFastFlingThreshold;
-
- // ConversationMessageView that is currently selected
- private ConversationMessageView mSelectedMessage;
-
- // Attachment data for the attachment within the selected message that was long pressed
- private MessagePartData mSelectedAttachment;
-
- // Normally, as soon as draft message is loaded, we trust the UI state held in
- // ComposeMessageView to be the only source of truth (incl. the conversation self id). However,
- // there can be external events that forces the UI state to change, such as SIM state changes
- // or SIM auto-switching on receiving a message. This receiver is used to receive such
- // local broadcast messages and reflect the change in the UI.
- private final BroadcastReceiver mConversationSelfIdChangeReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(final Context context, final Intent intent) {
- final String conversationId =
- intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
- final String selfId =
- intent.getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_SELF_ID);
- Assert.notNull(conversationId);
- Assert.notNull(selfId);
- if (TextUtils.equals(mBinding.getData().getConversationId(), conversationId)) {
- mComposeMessageView.updateConversationSelfIdOnExternalChange(selfId);
- }
- }
- };
-
- // Flag to prevent writing draft to DB on pause
- private boolean mSuppressWriteDraft;
-
- // Indicates whether local draft should be cleared due to external draft changes that must
- // be reloaded from db
- private boolean mClearLocalDraft;
- private ImmutableBindingRef<DraftMessageData> mDraftMessageDataModel;
-
- private boolean isScrolledToBottom() {
- if (mRecyclerView.getChildCount() == 0) {
- return true;
- }
- final View lastView = mRecyclerView.getChildAt(mRecyclerView.getChildCount() - 1);
- int lastVisibleItem = ((LinearLayoutManager) mRecyclerView
- .getLayoutManager()).findLastVisibleItemPosition();
- if (lastVisibleItem < 0) {
- // If the recyclerView height is 0, then the last visible item position is -1
- // Try to compute the position of the last item, even though it's not visible
- final long id = mRecyclerView.getChildItemId(lastView);
- final RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForItemId(id);
- if (holder != null) {
- lastVisibleItem = holder.getAdapterPosition();
- }
- }
- final int totalItemCount = mRecyclerView.getAdapter().getItemCount();
- final boolean isAtBottom = (lastVisibleItem + 1 == totalItemCount);
- return isAtBottom && lastView.getBottom() <= mRecyclerView.getHeight();
- }
-
- private void scrollToBottom(final boolean smoothScroll) {
- if (mAdapter.getItemCount() > 0) {
- scrollToPosition(mAdapter.getItemCount() - 1, smoothScroll);
- }
- }
-
- private int mScrollToDismissThreshold;
- private final RecyclerView.OnScrollListener mListScrollListener =
- new RecyclerView.OnScrollListener() {
- // Keeps track of cumulative scroll delta during a scroll event, which we may use to
- // hide the media picker & co.
- private int mCumulativeScrollDelta;
- private boolean mScrollToDismissHandled;
- private boolean mWasScrolledToBottom = true;
- private int mScrollState = RecyclerView.SCROLL_STATE_IDLE;
-
- @Override
- public void onScrollStateChanged(final RecyclerView view, final int newState) {
- if (newState == RecyclerView.SCROLL_STATE_IDLE) {
- // Reset scroll states.
- mCumulativeScrollDelta = 0;
- mScrollToDismissHandled = false;
- } else if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
- mRecyclerView.getItemAnimator().endAnimations();
- }
- mScrollState = newState;
- }
-
- @Override
- public void onScrolled(final RecyclerView view, final int dx, final int dy) {
- if (mScrollState == RecyclerView.SCROLL_STATE_DRAGGING &&
- !mScrollToDismissHandled) {
- mCumulativeScrollDelta += dy;
- // Dismiss the keyboard only when the user scroll up (into the past).
- if (mCumulativeScrollDelta < -mScrollToDismissThreshold) {
- mComposeMessageView.hideAllComposeInputs(false /* animate */);
- mScrollToDismissHandled = true;
- }
- }
- if (mWasScrolledToBottom != isScrolledToBottom()) {
- mConversationComposeDivider.animate().alpha(isScrolledToBottom() ? 0 : 1);
- mWasScrolledToBottom = isScrolledToBottom();
- }
- }
- };
-
- private final ActionMode.Callback mMessageActionModeCallback = new ActionMode.Callback() {
- @Override
- public boolean onCreateActionMode(final ActionMode actionMode, final Menu menu) {
- if (mSelectedMessage == null) {
- return false;
- }
- final ConversationMessageData data = mSelectedMessage.getData();
- final MenuInflater menuInflater = getActivity().getMenuInflater();
- menuInflater.inflate(R.menu.conversation_fragment_select_menu, menu);
- menu.findItem(R.id.action_download).setVisible(data.getShowDownloadMessage());
- menu.findItem(R.id.action_send).setVisible(data.getShowResendMessage());
-
- // ShareActionProvider does not work with ActionMode. So we use a normal menu item.
- menu.findItem(R.id.share_message_menu).setVisible(data.getCanForwardMessage());
- menu.findItem(R.id.save_attachment).setVisible(mSelectedAttachment != null);
- menu.findItem(R.id.forward_message_menu).setVisible(data.getCanForwardMessage());
-
- // TODO: We may want to support copying attachments in the future, but it's
- // unclear which attachment to pick when we make this context menu at the message level
- // instead of the part level
- menu.findItem(R.id.copy_text).setVisible(data.getCanCopyMessageToClipboard());
-
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(final ActionMode actionMode, final Menu menu) {
- return true;
- }
-
- @Override
- public boolean onActionItemClicked(final ActionMode actionMode, final MenuItem menuItem) {
- final ConversationMessageData data = mSelectedMessage.getData();
- final String messageId = data.getMessageId();
- switch (menuItem.getItemId()) {
- case R.id.save_attachment:
- if (OsUtil.hasStoragePermission()) {
- final SaveAttachmentTask saveAttachmentTask = new SaveAttachmentTask(
- getActivity());
- for (final MessagePartData part : data.getAttachments()) {
- saveAttachmentTask.addAttachmentToSave(part.getContentUri(),
- part.getContentType());
- }
- if (saveAttachmentTask.getAttachmentCount() > 0) {
- saveAttachmentTask.executeOnThreadPool();
- mHost.dismissActionMode();
- }
- } else {
- getActivity().requestPermissions(
- new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, 0);
- }
- return true;
- case R.id.action_delete_message:
- if (mSelectedMessage != null) {
- deleteMessage(messageId);
- }
- return true;
- case R.id.action_download:
- if (mSelectedMessage != null) {
- retryDownload(messageId);
- mHost.dismissActionMode();
- }
- return true;
- case R.id.action_send:
- if (mSelectedMessage != null) {
- retrySend(messageId);
- mHost.dismissActionMode();
- }
- return true;
- case R.id.copy_text:
- Assert.isTrue(data.hasText());
- final ClipboardManager clipboard = (ClipboardManager) getActivity()
- .getSystemService(Context.CLIPBOARD_SERVICE);
- clipboard.setPrimaryClip(
- ClipData.newPlainText(null /* label */, data.getText()));
- mHost.dismissActionMode();
- return true;
- case R.id.details_menu:
- MessageDetailsDialog.show(
- getActivity(), data, mBinding.getData().getParticipants(),
- mBinding.getData().getSelfParticipantById(data.getSelfParticipantId()));
- mHost.dismissActionMode();
- return true;
- case R.id.share_message_menu:
- shareMessage(data);
- mHost.dismissActionMode();
- return true;
- case R.id.forward_message_menu:
- // TODO: Currently we are forwarding one part at a time, instead of
- // the entire message. Change this to forwarding the entire message when we
- // use message-based cursor in conversation.
- final MessageData message = mBinding.getData().createForwardedMessage(data);
- UIIntents.get().launchForwardMessageActivity(getActivity(), message);
- mHost.dismissActionMode();
- return true;
- }
- return false;
- }
-
- private void shareMessage(final ConversationMessageData data) {
- // Figure out what to share.
- MessagePartData attachmentToShare = mSelectedAttachment;
- // If the user long-pressed on the background, we will share the text (if any)
- // or the first attachment.
- if (mSelectedAttachment == null
- && TextUtil.isAllWhitespace(data.getText())) {
- final List<MessagePartData> attachments = data.getAttachments();
- if (attachments.size() > 0) {
- attachmentToShare = attachments.get(0);
- }
- }
-
- final Intent shareIntent = new Intent();
- shareIntent.setAction(Intent.ACTION_SEND);
- if (attachmentToShare == null) {
- shareIntent.putExtra(Intent.EXTRA_TEXT, data.getText());
- shareIntent.setType("text/plain");
- } else {
- shareIntent.putExtra(
- Intent.EXTRA_STREAM, attachmentToShare.getContentUri());
- shareIntent.setType(attachmentToShare.getContentType());
- }
- final CharSequence title = getResources().getText(R.string.action_share);
- startActivity(Intent.createChooser(shareIntent, title));
- }
-
- @Override
- public void onDestroyActionMode(final ActionMode actionMode) {
- selectMessage(null);
- }
- };
-
- /**
- * {@inheritDoc} from Fragment
- */
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mFastFlingThreshold = getResources().getDimensionPixelOffset(
- R.dimen.conversation_fast_fling_threshold);
- mAdapter = new ConversationMessageAdapter(getActivity(), null, this,
- null,
- // Sets the item click listener on the Recycler item views.
- new View.OnClickListener() {
- @Override
- public void onClick(final View v) {
- final ConversationMessageView messageView = (ConversationMessageView) v;
- handleMessageClick(messageView);
- }
- },
- new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(final View view) {
- selectMessage((ConversationMessageView) view);
- return true;
- }
- }
- );
- }
-
- /**
- * setConversationInfo() may be called before or after onCreate(). When a user initiate a
- * conversation from compose, the ConversationActivity creates this fragment and calls
- * setConversationInfo(), so it happens before onCreate(). However, when the activity is
- * restored from saved instance state, the ConversationFragment is created automatically by
- * the fragment, before ConversationActivity has a chance to call setConversationInfo(). Since
- * the ability to start loading data depends on both methods being called, we need to start
- * loading when onActivityCreated() is called, which is guaranteed to happen after both.
- */
- @Override
- public void onActivityCreated(final Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- // Delay showing the message list until the participant list is loaded.
- mRecyclerView.setVisibility(View.INVISIBLE);
- mBinding.ensureBound();
- mBinding.getData().init(getLoaderManager(), mBinding);
-
- // Build the input manager with all its required dependencies and pass it along to the
- // compose message view.
- final ConversationInputManager inputManager = new ConversationInputManager(
- getActivity(), this, mComposeMessageView, mHost, getFragmentManagerToUse(),
- mBinding, mComposeMessageView.getDraftDataModel(), savedInstanceState);
- mComposeMessageView.setInputManager(inputManager);
- mComposeMessageView.setConversationDataModel(BindingBase.createBindingReference(mBinding));
- mHost.invalidateActionBar();
-
- mDraftMessageDataModel =
- BindingBase.createBindingReference(mComposeMessageView.getDraftDataModel());
- mDraftMessageDataModel.getData().addListener(this);
- }
-
- public void onAttachmentChoosen() {
- // Attachment has been choosen in the AttachmentChooserActivity, so clear local draft
- // and reload draft on resume.
- mClearLocalDraft = true;
- }
-
- private int getScrollToMessagePosition() {
- final Activity activity = getActivity();
- if (activity == null) {
- return -1;
- }
-
- final Intent intent = activity.getIntent();
- if (intent == null) {
- return -1;
- }
-
- return intent.getIntExtra(UIIntents.UI_INTENT_EXTRA_MESSAGE_POSITION, -1);
- }
-
- private void clearScrollToMessagePosition() {
- final Activity activity = getActivity();
- if (activity == null) {
- return;
- }
-
- final Intent intent = activity.getIntent();
- if (intent == null) {
- return;
- }
- intent.putExtra(UIIntents.UI_INTENT_EXTRA_MESSAGE_POSITION, -1);
- }
-
- private final Handler mHandler = new Handler();
-
- /**
- * {@inheritDoc} from Fragment
- */
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.conversation_fragment, container, false);
- mRecyclerView = (RecyclerView) view.findViewById(android.R.id.list);
- final LinearLayoutManager manager = new LinearLayoutManager(getActivity());
- manager.setStackFromEnd(true);
- manager.setReverseLayout(false);
- mRecyclerView.setHasFixedSize(true);
- mRecyclerView.setLayoutManager(manager);
- mRecyclerView.setItemAnimator(new DefaultItemAnimator() {
- private final List<ViewHolder> mAddAnimations = new ArrayList<ViewHolder>();
- private PopupTransitionAnimation mPopupTransitionAnimation;
-
- @Override
- public boolean animateAdd(final ViewHolder holder) {
- final ConversationMessageView view =
- (ConversationMessageView) holder.itemView;
- final ConversationMessageData data = view.getData();
- endAnimation(holder);
- final long timeSinceSend = System.currentTimeMillis() - data.getReceivedTimeStamp();
- if (data.getReceivedTimeStamp() ==
- InsertNewMessageAction.getLastSentMessageTimestamp() &&
- !data.getIsIncoming() &&
- timeSinceSend < MESSAGE_ANIMATION_MAX_WAIT) {
- final ConversationMessageBubbleView messageBubble =
- (ConversationMessageBubbleView) view
- .findViewById(R.id.message_content);
- final Rect startRect = UiUtils.getMeasuredBoundsOnScreen(mComposeMessageView);
- final View composeBubbleView = mComposeMessageView.findViewById(
- R.id.compose_message_text);
- final Rect composeBubbleRect =
- UiUtils.getMeasuredBoundsOnScreen(composeBubbleView);
- final AttachmentPreview attachmentView =
- (AttachmentPreview) mComposeMessageView.findViewById(
- R.id.attachment_draft_view);
- final Rect attachmentRect = UiUtils.getMeasuredBoundsOnScreen(attachmentView);
- if (attachmentView.getVisibility() == View.VISIBLE) {
- startRect.top = attachmentRect.top;
- } else {
- startRect.top = composeBubbleRect.top;
- }
- startRect.top -= view.getPaddingTop();
- startRect.bottom =
- composeBubbleRect.bottom;
- startRect.left += view.getPaddingRight();
-
- view.setAlpha(0);
- mPopupTransitionAnimation = new PopupTransitionAnimation(startRect, view);
- mPopupTransitionAnimation.setOnStartCallback(new Runnable() {
- @Override
- public void run() {
- final int startWidth = composeBubbleRect.width();
- attachmentView.onMessageAnimationStart();
- messageBubble.kickOffMorphAnimation(startWidth,
- messageBubble.findViewById(R.id.message_text_and_info)
- .getMeasuredWidth());
- }
- });
- mPopupTransitionAnimation.setOnStopCallback(new Runnable() {
- @Override
- public void run() {
- view.setAlpha(1);
- }
- });
- mPopupTransitionAnimation.startAfterLayoutComplete();
- mAddAnimations.add(holder);
- return true;
- } else {
- return super.animateAdd(holder);
- }
- }
-
- @Override
- public void endAnimation(final ViewHolder holder) {
- if (mAddAnimations.remove(holder)) {
- holder.itemView.clearAnimation();
- }
- super.endAnimation(holder);
- }
-
- @Override
- public void endAnimations() {
- for (final ViewHolder holder : mAddAnimations) {
- holder.itemView.clearAnimation();
- }
- mAddAnimations.clear();
- if (mPopupTransitionAnimation != null) {
- mPopupTransitionAnimation.cancel();
- }
- super.endAnimations();
- }
- });
- mRecyclerView.setAdapter(mAdapter);
-
- if (savedInstanceState != null) {
- mListState = savedInstanceState.getParcelable(SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY);
- }
-
- mConversationComposeDivider = view.findViewById(R.id.conversation_compose_divider);
- mScrollToDismissThreshold = ViewConfiguration.get(getActivity()).getScaledTouchSlop();
- mRecyclerView.addOnScrollListener(mListScrollListener);
- mFastScroller = ConversationFastScroller.addTo(mRecyclerView,
- UiUtils.isRtlMode() ? ConversationFastScroller.POSITION_LEFT_SIDE :
- ConversationFastScroller.POSITION_RIGHT_SIDE);
-
- mComposeMessageView = (ComposeMessageView)
- view.findViewById(R.id.message_compose_view_container);
- // Bind the compose message view to the DraftMessageData
- mComposeMessageView.bind(DataModel.get().createDraftMessageData(
- mBinding.getData().getConversationId()), this);
-
- return view;
- }
-
- private void scrollToPosition(final int targetPosition, final boolean smoothScroll) {
- if (smoothScroll) {
- final int maxScrollDelta = JUMP_SCROLL_THRESHOLD;
-
- final LinearLayoutManager layoutManager =
- (LinearLayoutManager) mRecyclerView.getLayoutManager();
- final int firstVisibleItemPosition =
- layoutManager.findFirstVisibleItemPosition();
- final int delta = targetPosition - firstVisibleItemPosition;
- final int intermediatePosition;
-
- if (delta > maxScrollDelta) {
- intermediatePosition = Math.max(0, targetPosition - maxScrollDelta);
- } else if (delta < -maxScrollDelta) {
- final int count = layoutManager.getItemCount();
- intermediatePosition = Math.min(count - 1, targetPosition + maxScrollDelta);
- } else {
- intermediatePosition = -1;
- }
- if (intermediatePosition != -1) {
- mRecyclerView.scrollToPosition(intermediatePosition);
- }
- mRecyclerView.smoothScrollToPosition(targetPosition);
- } else {
- mRecyclerView.scrollToPosition(targetPosition);
- }
- }
-
- private int getScrollPositionFromBottom() {
- final LinearLayoutManager layoutManager =
- (LinearLayoutManager) mRecyclerView.getLayoutManager();
- final int lastVisibleItem =
- layoutManager.findLastVisibleItemPosition();
- return Math.max(mAdapter.getItemCount() - 1 - lastVisibleItem, 0);
- }
-
- /**
- * Display a photo using the Photoviewer component.
- */
- @Override
- public void displayPhoto(final Uri photoUri, final Rect imageBounds, final boolean isDraft) {
- displayPhoto(photoUri, imageBounds, isDraft, mConversationId, getActivity());
- }
-
- public static void displayPhoto(final Uri photoUri, final Rect imageBounds,
- final boolean isDraft, final String conversationId, final Activity activity) {
- final Uri imagesUri =
- isDraft ? MessagingContentProvider.buildDraftImagesUri(conversationId)
- : MessagingContentProvider.buildConversationImagesUri(conversationId);
- UIIntents.get().launchFullScreenPhotoViewer(
- activity, photoUri, imageBounds, imagesUri);
- }
-
- private void selectMessage(final ConversationMessageView messageView) {
- selectMessage(messageView, null /* attachment */);
- }
-
- private void selectMessage(final ConversationMessageView messageView,
- final MessagePartData attachment) {
- mSelectedMessage = messageView;
- if (mSelectedMessage == null) {
- mAdapter.setSelectedMessage(null);
- mHost.dismissActionMode();
- mSelectedAttachment = null;
- return;
- }
- mSelectedAttachment = attachment;
- mAdapter.setSelectedMessage(messageView.getData().getMessageId());
- mHost.startActionMode(mMessageActionModeCallback);
- }
-
- @Override
- public void onSaveInstanceState(final Bundle outState) {
- super.onSaveInstanceState(outState);
- if (mListState != null) {
- outState.putParcelable(SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY, mListState);
- }
- mComposeMessageView.saveInputState(outState);
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- if (mIncomingDraft == null) {
- mComposeMessageView.requestDraftMessage(mClearLocalDraft);
- } else {
- mComposeMessageView.setDraftMessage(mIncomingDraft);
- mIncomingDraft = null;
- }
- mClearLocalDraft = false;
-
- // On resume, check if there's a pending request for resuming message compose. This
- // may happen when the user commits the contact selection for a group conversation and
- // goes from compose back to the conversation fragment.
- if (mHost.shouldResumeComposeMessage()) {
- mComposeMessageView.resumeComposeMessage();
- }
-
- setConversationFocus();
-
- // On resume, invalidate all message views to show the updated timestamp.
- mAdapter.notifyDataSetChanged();
-
- LocalBroadcastManager.getInstance(getActivity()).registerReceiver(
- mConversationSelfIdChangeReceiver,
- new IntentFilter(UIIntents.CONVERSATION_SELF_ID_CHANGE_BROADCAST_ACTION));
- }
-
- void setConversationFocus() {
- if (mHost.isActiveAndFocused()) {
- mBinding.getData().setFocus();
- }
- }
-
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- if (mHost.getActionMode() != null) {
- return;
- }
-
- inflater.inflate(R.menu.conversation_menu, menu);
-
- final ConversationData data = mBinding.getData();
-
- // Disable the "people & options" item if we haven't loaded participants yet.
- menu.findItem(R.id.action_people_and_options).setEnabled(data.getParticipantsLoaded());
-
- // See if we can show add contact action.
- final ParticipantData participant = data.getOtherParticipant();
- final boolean addContactActionVisible = (participant != null
- && TextUtils.isEmpty(participant.getLookupKey()));
- menu.findItem(R.id.action_add_contact).setVisible(addContactActionVisible);
-
- // See if we should show archive or unarchive.
- final boolean isArchived = data.getIsArchived();
- menu.findItem(R.id.action_archive).setVisible(!isArchived);
- menu.findItem(R.id.action_unarchive).setVisible(isArchived);
-
- // Conditionally enable the phone call button.
- final boolean supportCallAction = (PhoneUtils.getDefault().isVoiceCapable() &&
- data.getParticipantPhoneNumber() != null);
- menu.findItem(R.id.action_call).setVisible(supportCallAction);
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case R.id.action_people_and_options:
- Assert.isTrue(mBinding.getData().getParticipantsLoaded());
- UIIntents.get().launchPeopleAndOptionsActivity(getActivity(), mConversationId);
- return true;
-
- case R.id.action_call:
- final String phoneNumber = mBinding.getData().getParticipantPhoneNumber();
- Assert.notNull(phoneNumber);
- final View targetView = getActivity().findViewById(R.id.action_call);
- Point centerPoint;
- if (targetView != null) {
- final int screenLocation[] = new int[2];
- targetView.getLocationOnScreen(screenLocation);
- final int centerX = screenLocation[0] + targetView.getWidth() / 2;
- final int centerY = screenLocation[1] + targetView.getHeight() / 2;
- centerPoint = new Point(centerX, centerY);
- } else {
- // In the overflow menu, just use the center of the screen.
- final Display display = getActivity().getWindowManager().getDefaultDisplay();
- centerPoint = new Point(display.getWidth() / 2, display.getHeight() / 2);
- }
- UIIntents.get().launchPhoneCallActivity(getActivity(), phoneNumber, centerPoint);
- return true;
-
- case R.id.action_archive:
- mBinding.getData().archiveConversation(mBinding);
- closeConversation(mConversationId);
- return true;
-
- case R.id.action_unarchive:
- mBinding.getData().unarchiveConversation(mBinding);
- return true;
-
- case R.id.action_settings:
- return true;
-
- case R.id.action_add_contact:
- final ParticipantData participant = mBinding.getData().getOtherParticipant();
- Assert.notNull(participant);
- final String destination = participant.getNormalizedDestination();
- final Uri avatarUri = AvatarUriUtil.createAvatarUri(participant);
- (new AddContactsConfirmationDialog(getActivity(), avatarUri, destination)).show();
- return true;
-
- case R.id.action_delete:
- if (isReadyForAction()) {
- new AlertDialog.Builder(getActivity())
- .setTitle(getResources().getQuantityString(
- R.plurals.delete_conversations_confirmation_dialog_title, 1))
- .setPositiveButton(R.string.delete_conversation_confirmation_button,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog,
- final int button) {
- deleteConversation();
- }
- })
- .setNegativeButton(R.string.delete_conversation_decline_button, null)
- .show();
- } else {
- warnOfMissingActionConditions(false /*sending*/,
- null /*commandToRunAfterActionConditionResolved*/);
- }
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- /**
- * {@inheritDoc} from ConversationDataListener
- */
- @Override
- public void onConversationMessagesCursorUpdated(final ConversationData data,
- final Cursor cursor, final ConversationMessageData newestMessage,
- final boolean isSync) {
- mBinding.ensureBound(data);
-
- // This needs to be determined before swapping cursor, which may change the scroll state.
- final boolean scrolledToBottom = isScrolledToBottom();
- final int positionFromBottom = getScrollPositionFromBottom();
-
- // If participants not loaded, assume 1:1 since that's the 99% case
- final boolean oneOnOne =
- !data.getParticipantsLoaded() || data.getOtherParticipant() != null;
- mAdapter.setOneOnOne(oneOnOne, false /* invalidate */);
-
- // Ensure that the action bar is updated with the current data.
- invalidateOptionsMenu();
- final Cursor oldCursor = mAdapter.swapCursor(cursor);
-
- if (cursor != null && oldCursor == null) {
- if (mListState != null) {
- mRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);
- // RecyclerView restores scroll states without triggering scroll change events, so
- // we need to manually ensure that they are correctly handled.
- mListScrollListener.onScrolled(mRecyclerView, 0, 0);
- }
- }
-
- if (isSync) {
- // This is a message sync. Syncing messages changes cursor item count, which would
- // implicitly change RV's scroll position. We'd like the RV to keep scrolled to the same
- // relative position from the bottom (because RV is stacked from bottom), so that it
- // stays relatively put as we sync.
- final int position = Math.max(mAdapter.getItemCount() - 1 - positionFromBottom, 0);
- scrollToPosition(position, false /* smoothScroll */);
- } else if (newestMessage != null) {
- // Show a snack bar notification if we are not scrolled to the bottom and the new
- // message is an incoming message.
- if (!scrolledToBottom && newestMessage.getIsIncoming()) {
- // If the conversation activity is started but not resumed (if another dialog
- // activity was in the foregrond), we will show a system notification instead of
- // the snack bar.
- if (mBinding.getData().isFocused()) {
- UiUtils.showSnackBarWithCustomAction(getActivity(),
- getView().getRootView(),
- getString(R.string.in_conversation_notify_new_message_text),
- SnackBar.Action.createCustomAction(new Runnable() {
- @Override
- public void run() {
- scrollToBottom(true /* smoothScroll */);
- mComposeMessageView.hideAllComposeInputs(false /* animate */);
- }
- },
- getString(R.string.in_conversation_notify_new_message_action)),
- null /* interactions */,
- SnackBar.Placement.above(mComposeMessageView));
- }
- } else {
- // We are either already scrolled to the bottom or this is an outgoing message,
- // scroll to the bottom to reveal it.
- // Don't smooth scroll if we were already at the bottom; instead, we scroll
- // immediately so RecyclerView's view animation will take place.
- scrollToBottom(!scrolledToBottom);
- }
- }
-
- if (cursor != null) {
- mHost.onConversationMessagesUpdated(cursor.getCount());
-
- // Are we coming from a widget click where we're told to scroll to a particular item?
- final int scrollToPos = getScrollToMessagePosition();
- if (scrollToPos >= 0) {
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.VERBOSE)) {
- LogUtil.v(LogUtil.BUGLE_TAG, "onConversationMessagesCursorUpdated " +
- " scrollToPos: " + scrollToPos +
- " cursorCount: " + cursor.getCount());
- }
- scrollToPosition(scrollToPos, true /*smoothScroll*/);
- clearScrollToMessagePosition();
- }
- }
-
- mHost.invalidateActionBar();
- }
-
- /**
- * {@inheritDoc} from ConversationDataListener
- */
- @Override
- public void onConversationMetadataUpdated(final ConversationData conversationData) {
- mBinding.ensureBound(conversationData);
-
- if (mSelectedMessage != null && mSelectedAttachment != null) {
- // We may have just sent a message and the temp attachment we selected is now gone.
- // and it was replaced with some new attachment. Since we don't know which one it
- // is we shouldn't reselect it (unless there is just one) In the multi-attachment
- // case we would just deselect the message and allow the user to reselect, otherwise we
- // may act on old temp data and may crash.
- final List<MessagePartData> currentAttachments = mSelectedMessage.getData().getAttachments();
- if (currentAttachments.size() == 1) {
- mSelectedAttachment = currentAttachments.get(0);
- } else if (!currentAttachments.contains(mSelectedAttachment)) {
- selectMessage(null);
- }
- }
- // Ensure that the action bar is updated with the current data.
- invalidateOptionsMenu();
- mHost.onConversationMetadataUpdated();
- mAdapter.notifyDataSetChanged();
- }
-
- public void setConversationInfo(final Context context, final String conversationId,
- final MessageData draftData) {
- // TODO: Eventually I would like the Factory to implement
- // Factory.get().bindConversationData(mBinding, getActivity(), this, conversationId));
- if (!mBinding.isBound()) {
- mConversationId = conversationId;
- mIncomingDraft = draftData;
- mBinding.bind(DataModel.get().createConversationData(context, this, conversationId));
- } else {
- Assert.isTrue(TextUtils.equals(mBinding.getData().getConversationId(), conversationId));
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- // Unbind all the views that we bound to data
- if (mComposeMessageView != null) {
- mComposeMessageView.unbind();
- }
-
- // And unbind this fragment from its data
- mBinding.unbind();
- mConversationId = null;
- }
-
- void suppressWriteDraft() {
- mSuppressWriteDraft = true;
- }
-
- @Override
- public void onPause() {
- super.onPause();
- if (mComposeMessageView != null && !mSuppressWriteDraft) {
- mComposeMessageView.writeDraftMessage();
- }
- mSuppressWriteDraft = false;
- mBinding.getData().unsetFocus();
- mListState = mRecyclerView.getLayoutManager().onSaveInstanceState();
-
- LocalBroadcastManager.getInstance(getActivity())
- .unregisterReceiver(mConversationSelfIdChangeReceiver);
- }
-
- @Override
- public void onConfigurationChanged(final Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- mRecyclerView.getItemAnimator().endAnimations();
- }
-
- // TODO: Remove isBound and replace it with ensureBound after b/15704674.
- public boolean isBound() {
- return mBinding.isBound();
- }
-
- private FragmentManager getFragmentManagerToUse() {
- return OsUtil.isAtLeastJB_MR1() ? getChildFragmentManager() : getFragmentManager();
- }
-
- public MediaPicker getMediaPicker() {
- return (MediaPicker) getFragmentManagerToUse().findFragmentByTag(
- MediaPicker.FRAGMENT_TAG);
- }
-
- @Override
- public void sendMessage(final MessageData message) {
- if (isReadyForAction()) {
- if (ensureKnownRecipients()) {
- // Merge the caption text from attachments into the text body of the messages
- message.consolidateText();
-
- mBinding.getData().sendMessage(mBinding, message);
- mComposeMessageView.resetMediaPickerState();
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG, "Message can't be sent: conv participants not loaded");
- }
- } else {
- warnOfMissingActionConditions(true /*sending*/,
- new Runnable() {
- @Override
- public void run() {
- sendMessage(message);
- }
- });
- }
- }
-
- public void setHost(final ConversationFragmentHost host) {
- mHost = host;
- }
-
- public String getConversationName() {
- return mBinding.getData().getConversationName();
- }
-
- @Override
- public void onComposeEditTextFocused() {
- mHost.onStartComposeMessage();
- }
-
- @Override
- public void onAttachmentsCleared() {
- // When attachments are removed, reset transient media picker state such as image selection.
- mComposeMessageView.resetMediaPickerState();
- }
-
- /**
- * Called to check if all conditions are nominal and a "go" for some action, such as deleting
- * a message, that requires this app to be the default app. This is also a precondition
- * required for sending a draft.
- * @return true if all conditions are nominal and we're ready to send a message
- */
- @Override
- public boolean isReadyForAction() {
- return UiUtils.isReadyForAction();
- }
-
- /**
- * When there's some condition that prevents an operation, such as sending a message,
- * call warnOfMissingActionConditions to put up a snackbar and allow the user to repair
- * that condition.
- * @param sending - true if we're called during a sending operation
- * @param commandToRunAfterActionConditionResolved - a runnable to run after the user responds
- * positively to the condition prompt and resolves the condition. If null,
- * the user will be shown a toast to tap the send button again.
- */
- @Override
- public void warnOfMissingActionConditions(final boolean sending,
- final Runnable commandToRunAfterActionConditionResolved) {
- if (mChangeDefaultSmsAppHelper == null) {
- mChangeDefaultSmsAppHelper = new ChangeDefaultSmsAppHelper();
- }
- mChangeDefaultSmsAppHelper.warnOfMissingActionConditions(sending,
- commandToRunAfterActionConditionResolved, mComposeMessageView,
- getView().getRootView(),
- getActivity(), this);
- }
-
- private boolean ensureKnownRecipients() {
- final ConversationData conversationData = mBinding.getData();
-
- if (!conversationData.getParticipantsLoaded()) {
- // We can't tell yet whether or not we have an unknown recipient
- return false;
- }
-
- final ConversationParticipantsData participants = conversationData.getParticipants();
- for (final ParticipantData participant : participants) {
-
-
- if (participant.isUnknownSender()) {
- UiUtils.showToast(R.string.unknown_sender);
- return false;
- }
- }
-
- return true;
- }
-
- public void retryDownload(final String messageId) {
- if (isReadyForAction()) {
- mBinding.getData().downloadMessage(mBinding, messageId);
- } else {
- warnOfMissingActionConditions(false /*sending*/,
- null /*commandToRunAfterActionConditionResolved*/);
- }
- }
-
- public void retrySend(final String messageId) {
- if (isReadyForAction()) {
- if (ensureKnownRecipients()) {
- mBinding.getData().resendMessage(mBinding, messageId);
- }
- } else {
- warnOfMissingActionConditions(true /*sending*/,
- new Runnable() {
- @Override
- public void run() {
- retrySend(messageId);
- }
-
- });
- }
- }
-
- void deleteMessage(final String messageId) {
- if (isReadyForAction()) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
- .setTitle(R.string.delete_message_confirmation_dialog_title)
- .setMessage(R.string.delete_message_confirmation_dialog_text)
- .setPositiveButton(R.string.delete_message_confirmation_button,
- new OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- mBinding.getData().deleteMessage(mBinding, messageId);
- }
- })
- .setNegativeButton(android.R.string.cancel, null);
- if (OsUtil.isAtLeastJB_MR1()) {
- builder.setOnDismissListener(new OnDismissListener() {
- @Override
- public void onDismiss(final DialogInterface dialog) {
- mHost.dismissActionMode();
- }
- });
- } else {
- builder.setOnCancelListener(new OnCancelListener() {
- @Override
- public void onCancel(final DialogInterface dialog) {
- mHost.dismissActionMode();
- }
- });
- }
- builder.create().show();
- } else {
- warnOfMissingActionConditions(false /*sending*/,
- null /*commandToRunAfterActionConditionResolved*/);
- mHost.dismissActionMode();
- }
- }
-
- public void deleteConversation() {
- if (isReadyForAction()) {
- final Context context = getActivity();
- mBinding.getData().deleteConversation(mBinding);
- closeConversation(mConversationId);
- } else {
- warnOfMissingActionConditions(false /*sending*/,
- null /*commandToRunAfterActionConditionResolved*/);
- }
- }
-
- @Override
- public void closeConversation(final String conversationId) {
- if (TextUtils.equals(conversationId, mConversationId)) {
- mHost.onFinishCurrentConversation();
- // TODO: Explicitly transition to ConversationList (or just go back)?
- }
- }
-
- @Override
- public void onConversationParticipantDataLoaded(final ConversationData data) {
- mBinding.ensureBound(data);
- if (mBinding.getData().getParticipantsLoaded()) {
- final boolean oneOnOne = mBinding.getData().getOtherParticipant() != null;
- mAdapter.setOneOnOne(oneOnOne, true /* invalidate */);
-
- // refresh the options menu which will enable the "people & options" item.
- invalidateOptionsMenu();
-
- mHost.invalidateActionBar();
-
- mRecyclerView.setVisibility(View.VISIBLE);
- mHost.onConversationParticipantDataLoaded
- (mBinding.getData().getNumberOfParticipantsExcludingSelf());
- }
- }
-
- @Override
- public void onSubscriptionListDataLoaded(final ConversationData data) {
- mBinding.ensureBound(data);
- mAdapter.notifyDataSetChanged();
- }
-
- @Override
- public void promptForSelfPhoneNumber() {
- if (mComposeMessageView != null) {
- // Avoid bug in system which puts soft keyboard over dialog after orientation change
- ImeUtil.hideSoftInput(getActivity(), mComposeMessageView);
- }
-
- final FragmentTransaction ft = getActivity().getFragmentManager().beginTransaction();
- final EnterSelfPhoneNumberDialog dialog = EnterSelfPhoneNumberDialog
- .newInstance(getConversationSelfSubId());
- dialog.setTargetFragment(this, 0/*requestCode*/);
- dialog.show(ft, null/*tag*/);
- }
-
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
- if (mChangeDefaultSmsAppHelper == null) {
- mChangeDefaultSmsAppHelper = new ChangeDefaultSmsAppHelper();
- }
- mChangeDefaultSmsAppHelper.handleChangeDefaultSmsResult(requestCode, resultCode, null);
- }
-
- public boolean hasMessages() {
- return mAdapter != null && mAdapter.getItemCount() > 0;
- }
-
- public boolean onBackPressed() {
- if (mComposeMessageView.onBackPressed()) {
- return true;
- }
- return false;
- }
-
- public boolean onNavigationUpPressed() {
- return mComposeMessageView.onNavigationUpPressed();
- }
-
- @Override
- public boolean onAttachmentClick(final ConversationMessageView messageView,
- final MessagePartData attachment, final Rect imageBounds, final boolean longPress) {
- if (longPress) {
- selectMessage(messageView, attachment);
- return true;
- } else if (messageView.getData().getOneClickResendMessage()) {
- handleMessageClick(messageView);
- return true;
- }
-
- if (attachment.isImage()) {
- displayPhoto(attachment.getContentUri(), imageBounds, false /* isDraft */);
- }
-
- if (attachment.isVCard()) {
- UIIntents.get().launchVCardDetailActivity(getActivity(), attachment.getContentUri());
- }
-
- return false;
- }
-
- private void handleMessageClick(final ConversationMessageView messageView) {
- if (messageView != mSelectedMessage) {
- final ConversationMessageData data = messageView.getData();
- final boolean isReadyToSend = isReadyForAction();
- if (data.getOneClickResendMessage()) {
- // Directly resend the message on tap if it's failed
- retrySend(data.getMessageId());
- selectMessage(null);
- } else if (data.getShowResendMessage() && isReadyToSend) {
- // Select the message to show the resend/download/delete options
- selectMessage(messageView);
- } else if (data.getShowDownloadMessage() && isReadyToSend) {
- // Directly download the message on tap
- retryDownload(data.getMessageId());
- } else {
- // Let the toast from warnOfMissingActionConditions show and skip
- // selecting
- warnOfMissingActionConditions(false /*sending*/,
- null /*commandToRunAfterActionConditionResolved*/);
- selectMessage(null);
- }
- } else {
- selectMessage(null);
- }
- }
-
- private static class AttachmentToSave {
- public final Uri uri;
- public final String contentType;
- public Uri persistedUri;
-
- AttachmentToSave(final Uri uri, final String contentType) {
- this.uri = uri;
- this.contentType = contentType;
- }
- }
-
- public static class SaveAttachmentTask extends SafeAsyncTask<Void, Void, Void> {
- private final Context mContext;
- private final List<AttachmentToSave> mAttachmentsToSave = new ArrayList<>();
-
- public SaveAttachmentTask(final Context context, final Uri contentUri,
- final String contentType) {
- mContext = context;
- addAttachmentToSave(contentUri, contentType);
- }
-
- public SaveAttachmentTask(final Context context) {
- mContext = context;
- }
-
- public void addAttachmentToSave(final Uri contentUri, final String contentType) {
- mAttachmentsToSave.add(new AttachmentToSave(contentUri, contentType));
- }
-
- public int getAttachmentCount() {
- return mAttachmentsToSave.size();
- }
-
- @Override
- protected Void doInBackgroundTimed(final Void... arg) {
- final File appDir = new File(Environment.getExternalStoragePublicDirectory(
- Environment.DIRECTORY_PICTURES),
- mContext.getResources().getString(R.string.app_name));
- final File downloadDir = Environment.getExternalStoragePublicDirectory(
- Environment.DIRECTORY_DOWNLOADS);
- for (final AttachmentToSave attachment : mAttachmentsToSave) {
- final boolean isImageOrVideo = ContentType.isImageType(attachment.contentType)
- || ContentType.isVideoType(attachment.contentType);
- attachment.persistedUri = UriUtil.persistContent(attachment.uri,
- isImageOrVideo ? appDir : downloadDir, attachment.contentType);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(final Void result) {
- int failCount = 0;
- int imageCount = 0;
- int videoCount = 0;
- int otherCount = 0;
- for (final AttachmentToSave attachment : mAttachmentsToSave) {
- if (attachment.persistedUri == null) {
- failCount++;
- continue;
- }
-
- // Inform MediaScanner about the new file
- final Intent scanFileIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
- scanFileIntent.setData(attachment.persistedUri);
- mContext.sendBroadcast(scanFileIntent);
-
- if (ContentType.isImageType(attachment.contentType)) {
- imageCount++;
- } else if (ContentType.isVideoType(attachment.contentType)) {
- videoCount++;
- } else {
- otherCount++;
- // Inform DownloadManager of the file so it will show in the "downloads" app
- final DownloadManager downloadManager =
- (DownloadManager) mContext.getSystemService(
- Context.DOWNLOAD_SERVICE);
- final String filePath = attachment.persistedUri.getPath();
- final File file = new File(filePath);
-
- if (file.exists()) {
- downloadManager.addCompletedDownload(
- file.getName() /* title */,
- mContext.getString(
- R.string.attachment_file_description) /* description */,
- true /* isMediaScannerScannable */,
- attachment.contentType,
- file.getAbsolutePath(),
- file.length(),
- false /* showNotification */);
- }
- }
- }
-
- String message;
- if (failCount > 0) {
- message = mContext.getResources().getQuantityString(
- R.plurals.attachment_save_error, failCount, failCount);
- } else {
- int messageId = R.plurals.attachments_saved;
- if (otherCount > 0) {
- if (imageCount + videoCount == 0) {
- messageId = R.plurals.attachments_saved_to_downloads;
- }
- } else {
- if (videoCount == 0) {
- messageId = R.plurals.photos_saved_to_album;
- } else if (imageCount == 0) {
- messageId = R.plurals.videos_saved_to_album;
- } else {
- messageId = R.plurals.attachments_saved_to_album;
- }
- }
- final String appName = mContext.getResources().getString(R.string.app_name);
- final int count = imageCount + videoCount + otherCount;
- message = mContext.getResources().getQuantityString(
- messageId, count, count, appName);
- }
- UiUtils.showToastAtBottom(message);
- }
- }
-
- private void invalidateOptionsMenu() {
- final Activity activity = getActivity();
- // TODO: Add the supportInvalidateOptionsMenu call to the host activity.
- if (activity == null || !(activity instanceof BugleActionBarActivity)) {
- return;
- }
- ((BugleActionBarActivity) activity).supportInvalidateOptionsMenu();
- }
-
- @Override
- public void setOptionsMenuVisibility(final boolean visible) {
- setHasOptionsMenu(visible);
- }
-
- @Override
- public int getConversationSelfSubId() {
- final String selfParticipantId = mComposeMessageView.getConversationSelfId();
- final ParticipantData self = mBinding.getData().getSelfParticipantById(selfParticipantId);
- // If the self id or the self participant data hasn't been loaded yet, fallback to
- // the default setting.
- return self == null ? ParticipantData.DEFAULT_SELF_SUB_ID : self.getSubId();
- }
-
- @Override
- public void invalidateActionBar() {
- mHost.invalidateActionBar();
- }
-
- @Override
- public void dismissActionMode() {
- mHost.dismissActionMode();
- }
-
- @Override
- public void selectSim(final SubscriptionListEntry subscriptionData) {
- mComposeMessageView.selectSim(subscriptionData);
- mHost.onStartComposeMessage();
- }
-
- @Override
- public void onStartComposeMessage() {
- mHost.onStartComposeMessage();
- }
-
- @Override
- public SubscriptionListEntry getSubscriptionEntryForSelfParticipant(
- final String selfParticipantId, final boolean excludeDefault) {
- // TODO: ConversationMessageView is the only one using this. We should probably
- // inject this into the view during binding in the ConversationMessageAdapter.
- return mBinding.getData().getSubscriptionEntryForSelfParticipant(selfParticipantId,
- excludeDefault);
- }
-
- @Override
- public SimSelectorView getSimSelectorView() {
- return (SimSelectorView) getView().findViewById(R.id.sim_selector);
- }
-
- @Override
- public MediaPicker createMediaPicker() {
- return new MediaPicker(getActivity());
- }
-
- @Override
- public void notifyOfAttachmentLoadFailed() {
- UiUtils.showToastAtBottom(R.string.attachment_load_failed_dialog_message);
- }
-
- @Override
- public void warnOfExceedingMessageLimit(final boolean sending, final boolean tooManyVideos) {
- warnOfExceedingMessageLimit(sending, mComposeMessageView, mConversationId,
- getActivity(), tooManyVideos);
- }
-
- public static void warnOfExceedingMessageLimit(final boolean sending,
- final ComposeMessageView composeMessageView, final String conversationId,
- final Activity activity, final boolean tooManyVideos) {
- final AlertDialog.Builder builder =
- new AlertDialog.Builder(activity)
- .setTitle(R.string.mms_attachment_limit_reached);
-
- if (sending) {
- if (tooManyVideos) {
- builder.setMessage(R.string.video_attachment_limit_exceeded_when_sending);
- } else {
- builder.setMessage(R.string.attachment_limit_reached_dialog_message_when_sending)
- .setNegativeButton(R.string.attachment_limit_reached_send_anyway,
- new OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog,
- final int which) {
- composeMessageView.sendMessageIgnoreMessageSizeLimit();
- }
- });
- }
- builder.setPositiveButton(android.R.string.ok, new OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- showAttachmentChooser(conversationId, activity);
- }});
- } else {
- builder.setMessage(R.string.attachment_limit_reached_dialog_message_when_composing)
- .setPositiveButton(android.R.string.ok, null);
- }
- builder.show();
- }
-
- @Override
- public void showAttachmentChooser() {
- showAttachmentChooser(mConversationId, getActivity());
- }
-
- public static void showAttachmentChooser(final String conversationId,
- final Activity activity) {
- UIIntents.get().launchAttachmentChooserActivity(activity,
- conversationId, REQUEST_CHOOSE_ATTACHMENTS);
- }
-
- private void updateActionAndStatusBarColor(final ActionBar actionBar) {
- final int themeColor = ConversationDrawables.get().getConversationThemeColor();
- actionBar.setBackgroundDrawable(new ColorDrawable(themeColor));
- UiUtils.setStatusBarColor(getActivity(), themeColor);
- }
-
- public void updateActionBar(final ActionBar actionBar) {
- if (mComposeMessageView == null || !mComposeMessageView.updateActionBar(actionBar)) {
- updateActionAndStatusBarColor(actionBar);
- // We update this regardless of whether or not the action bar is showing so that we
- // don't get a race when it reappears.
- actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
- actionBar.setDisplayHomeAsUpEnabled(true);
- // Reset the back arrow to its default
- actionBar.setHomeAsUpIndicator(0);
- View customView = actionBar.getCustomView();
- if (customView == null || customView.getId() != R.id.conversation_title_container) {
- final LayoutInflater inflator = (LayoutInflater)
- getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- customView = inflator.inflate(R.layout.action_bar_conversation_name, null);
- customView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View v) {
- onBackPressed();
- }
- });
- actionBar.setCustomView(customView);
- }
-
- final TextView conversationNameView =
- (TextView) customView.findViewById(R.id.conversation_title);
- final String conversationName = getConversationName();
- if (!TextUtils.isEmpty(conversationName)) {
- // RTL : To format conversation title if it happens to be phone numbers.
- final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
- final String formattedName = bidiFormatter.unicodeWrap(
- UiUtils.commaEllipsize(
- conversationName,
- conversationNameView.getPaint(),
- conversationNameView.getWidth(),
- getString(R.string.plus_one),
- getString(R.string.plus_n)).toString(),
- TextDirectionHeuristicsCompat.LTR);
- conversationNameView.setText(formattedName);
- // In case phone numbers are mixed in the conversation name, we need to vocalize it.
- final String vocalizedConversationName =
- AccessibilityUtil.getVocalizedPhoneNumber(getResources(), conversationName);
- conversationNameView.setContentDescription(vocalizedConversationName);
- getActivity().setTitle(conversationName);
- } else {
- final String appName = getString(R.string.app_name);
- conversationNameView.setText(appName);
- getActivity().setTitle(appName);
- }
-
- // When conversation is showing and media picker is not showing, then hide the action
- // bar only when we are in landscape mode, with IME open.
- if (mHost.isImeOpen() && UiUtils.isLandscapeMode()) {
- actionBar.hide();
- } else {
- actionBar.show();
- }
- }
- }
-
- @Override
- public boolean shouldShowSubjectEditor() {
- return true;
- }
-
- @Override
- public boolean shouldHideAttachmentsWhenSimSelectorShown() {
- return false;
- }
-
- @Override
- public void showHideSimSelector(final boolean show) {
- // no-op for now
- }
-
- @Override
- public int getSimSelectorItemLayoutId() {
- return R.layout.sim_selector_item_view;
- }
-
- @Override
- public Uri getSelfSendButtonIconUri() {
- return null; // use default button icon uri
- }
-
- @Override
- public int overrideCounterColor() {
- return -1; // don't override the color
- }
-
- @Override
- public void onAttachmentsChanged(final boolean haveAttachments) {
- // no-op for now
- }
-
- @Override
- public void onDraftChanged(final DraftMessageData data, final int changeFlags) {
- mDraftMessageDataModel.ensureBound(data);
- // We're specifically only interested in ATTACHMENTS_CHANGED from the widget. Ignore
- // other changes. When the widget changes an attachment, we need to reload the draft.
- if (changeFlags ==
- (DraftMessageData.WIDGET_CHANGED | DraftMessageData.ATTACHMENTS_CHANGED)) {
- mClearLocalDraft = true; // force a reload of the draft in onResume
- }
- }
-
- @Override
- public void onDraftAttachmentLimitReached(final DraftMessageData data) {
- // no-op for now
- }
-
- @Override
- public void onDraftAttachmentLoadFailed() {
- // no-op for now
- }
-
- @Override
- public int getAttachmentsClearedFlags() {
- return DraftMessageData.ATTACHMENTS_CHANGED;
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationInput.java b/src/com/android/messaging/ui/conversation/ConversationInput.java
deleted file mode 100644
index bf60aa8..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationInput.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * 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.os.Bundle;
-import android.support.v7.app.ActionBar;
-
-/**
- * The base class for a method of user input, e.g. media picker.
- */
-public abstract class ConversationInput {
- /**
- * The host component where all input components are contained. This is typically the
- * conversation fragment but may be mocked in test code.
- */
- public interface ConversationInputBase {
- boolean showHideInternal(final ConversationInput target, final boolean show,
- final boolean animate);
- String getInputStateKey(final ConversationInput input);
- void beginUpdate();
- void handleOnShow(final ConversationInput target);
- void endUpdate();
- }
-
- protected boolean mShowing;
- protected ConversationInputBase mConversationInputBase;
-
- public abstract boolean show(boolean animate);
- public abstract boolean hide(boolean animate);
-
- public ConversationInput(ConversationInputBase baseHost, final boolean isShowing) {
- mConversationInputBase = baseHost;
- mShowing = isShowing;
- }
-
- public boolean onBackPressed() {
- if (mShowing) {
- mConversationInputBase.showHideInternal(this, false /* show */, true /* animate */);
- return true;
- }
- return false;
- }
-
- public boolean onNavigationUpPressed() {
- return false;
- }
-
- /**
- * Toggle the visibility of this view.
- * @param animate
- * @return true if the view is now shown, false if it now hidden
- */
- public boolean toggle(final boolean animate) {
- mConversationInputBase.showHideInternal(this, !mShowing /* show */, true /* animate */);
- return mShowing;
- }
-
- public void saveState(final Bundle savedState) {
- savedState.putBoolean(mConversationInputBase.getInputStateKey(this), mShowing);
- }
-
- public void restoreState(final Bundle savedState) {
- // Things are hidden by default, so only handle show.
- if (savedState.getBoolean(mConversationInputBase.getInputStateKey(this))) {
- mConversationInputBase.showHideInternal(this, true /* show */, false /* animate */);
- }
- }
-
- public boolean updateActionBar(final ActionBar actionBar) {
- return false;
- }
-
- /**
- * Update our visibility flag in response to visibility change, both for actions
- * initiated by this class (through the show/hide methods), and for external changes
- * tracked by event listeners (e.g. ImeStateObserver, MediaPickerListener). As part of
- * handling an input showing, we will hide all other inputs to ensure they are mutually
- * exclusive.
- */
- protected void onVisibilityChanged(final boolean visible) {
- if (mShowing != visible) {
- mConversationInputBase.beginUpdate();
- mShowing = visible;
- if (visible) {
- mConversationInputBase.handleOnShow(this);
- }
- mConversationInputBase.endUpdate();
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationInputManager.java b/src/com/android/messaging/ui/conversation/ConversationInputManager.java
deleted file mode 100644
index e10abe7..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationInputManager.java
+++ /dev/null
@@ -1,550 +0,0 @@
-/*
- * 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.content.Context;
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.widget.EditText;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.binding.ImmutableBindingRef;
-import com.android.messaging.datamodel.data.ConversationData;
-import com.android.messaging.datamodel.data.ConversationData.ConversationDataListener;
-import com.android.messaging.datamodel.data.ConversationData.SimpleConversationDataListener;
-import com.android.messaging.datamodel.data.DraftMessageData;
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.PendingAttachmentData;
-import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
-import com.android.messaging.ui.ConversationDrawables;
-import com.android.messaging.ui.mediapicker.MediaPicker;
-import com.android.messaging.ui.mediapicker.MediaPicker.MediaPickerListener;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ImeUtil;
-import com.android.messaging.util.ImeUtil.ImeStateHost;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.Collection;
-
-/**
- * Manages showing/hiding/persisting different mutually exclusive UI components nested in
- * ConversationFragment that take user inputs, i.e. media picker, SIM selector and
- * IME keyboard (the IME keyboard is not owned by Bugle, but we try to model it the same way
- * as the other components).
- */
-public class ConversationInputManager implements ConversationInput.ConversationInputBase {
- /**
- * The host component where all input components are contained. This is typically the
- * conversation fragment but may be mocked in test code.
- */
- public interface ConversationInputHost extends DraftMessageSubscriptionDataProvider {
- void invalidateActionBar();
- void setOptionsMenuVisibility(boolean visible);
- void dismissActionMode();
- void selectSim(SubscriptionListEntry subscriptionData);
- void onStartComposeMessage();
- SimSelectorView getSimSelectorView();
- MediaPicker createMediaPicker();
- void showHideSimSelector(boolean show);
- int getSimSelectorItemLayoutId();
- }
-
- /**
- * The "sink" component where all inputs components will direct the user inputs to. This is
- * typically the ComposeMessageView but may be mocked in test code.
- */
- public interface ConversationInputSink {
- void onMediaItemsSelected(Collection<MessagePartData> items);
- void onMediaItemsUnselected(MessagePartData item);
- void onPendingAttachmentAdded(PendingAttachmentData pendingItem);
- void resumeComposeMessage();
- EditText getComposeEditText();
- void setAccessibility(boolean enabled);
- }
-
- private final ConversationInputHost mHost;
- private final ConversationInputSink mSink;
-
- /** Dependencies injected from the host during construction */
- private final FragmentManager mFragmentManager;
- private final Context mContext;
- private final ImeStateHost mImeStateHost;
- private final ImmutableBindingRef<ConversationData> mConversationDataModel;
- private final ImmutableBindingRef<DraftMessageData> mDraftDataModel;
-
- private final ConversationInput[] mInputs;
- private final ConversationMediaPicker mMediaInput;
- private final ConversationSimSelector mSimInput;
- private final ConversationImeKeyboard mImeInput;
- private int mUpdateCount;
-
- private final ImeUtil.ImeStateObserver mImeStateObserver = new ImeUtil.ImeStateObserver() {
- @Override
- public void onImeStateChanged(final boolean imeOpen) {
- mImeInput.onVisibilityChanged(imeOpen);
- }
- };
-
- private final ConversationDataListener mDataListener = new SimpleConversationDataListener() {
- @Override
- public void onConversationParticipantDataLoaded(ConversationData data) {
- mConversationDataModel.ensureBound(data);
- }
-
- @Override
- public void onSubscriptionListDataLoaded(ConversationData data) {
- mConversationDataModel.ensureBound(data);
- mSimInput.onSubscriptionListDataLoaded(data.getSubscriptionListData());
- }
- };
-
- public ConversationInputManager(
- final Context context,
- final ConversationInputHost host,
- final ConversationInputSink sink,
- final ImeStateHost imeStateHost,
- final FragmentManager fm,
- final BindingBase<ConversationData> conversationDataModel,
- final BindingBase<DraftMessageData> draftDataModel,
- final Bundle savedState) {
- mHost = host;
- mSink = sink;
- mFragmentManager = fm;
- mContext = context;
- mImeStateHost = imeStateHost;
- mConversationDataModel = BindingBase.createBindingReference(conversationDataModel);
- mDraftDataModel = BindingBase.createBindingReference(draftDataModel);
-
- // Register listeners on dependencies.
- mImeStateHost.registerImeStateObserver(mImeStateObserver);
- mConversationDataModel.getData().addConversationDataListener(mDataListener);
-
- // Initialize the inputs
- mMediaInput = new ConversationMediaPicker(this);
- mSimInput = new SimSelector(this);
- mImeInput = new ConversationImeKeyboard(this, mImeStateHost.isImeOpen());
- mInputs = new ConversationInput[] { mMediaInput, mSimInput, mImeInput };
-
- if (savedState != null) {
- for (int i = 0; i < mInputs.length; i++) {
- mInputs[i].restoreState(savedState);
- }
- }
- updateHostOptionsMenu();
- }
-
- public void onDetach() {
- mImeStateHost.unregisterImeStateObserver(mImeStateObserver);
- // Don't need to explicitly unregister for data model events. It will unregister all
- // listeners automagically on unbind.
- }
-
- public void onSaveInputState(final Bundle savedState) {
- for (int i = 0; i < mInputs.length; i++) {
- mInputs[i].saveState(savedState);
- }
- }
-
- @Override
- public String getInputStateKey(final ConversationInput input) {
- return input.getClass().getCanonicalName() + "_savedstate_";
- }
-
- public boolean onBackPressed() {
- for (int i = 0; i < mInputs.length; i++) {
- if (mInputs[i].onBackPressed()) {
- return true;
- }
- }
- return false;
- }
-
- public boolean onNavigationUpPressed() {
- for (int i = 0; i < mInputs.length; i++) {
- if (mInputs[i].onNavigationUpPressed()) {
- return true;
- }
- }
- return false;
- }
-
- public void resetMediaPickerState() {
- mMediaInput.resetViewHolderState();
- }
-
- public void showHideMediaPicker(final boolean show, final boolean animate) {
- showHideInternal(mMediaInput, show, animate);
- }
-
- /**
- * Show or hide the sim selector
- * @param show visibility
- * @param animate whether to animate the change in visibility
- * @return true if the state of the visibility was changed
- */
- public boolean showHideSimSelector(final boolean show, final boolean animate) {
- return showHideInternal(mSimInput, show, animate);
- }
-
- public void showHideImeKeyboard(final boolean show, final boolean animate) {
- showHideInternal(mImeInput, show, animate);
- }
-
- public void hideAllInputs(final boolean animate) {
- beginUpdate();
- for (int i = 0; i < mInputs.length; i++) {
- showHideInternal(mInputs[i], false, animate);
- }
- endUpdate();
- }
-
- /**
- * Toggle the visibility of the sim selector.
- * @param animate
- * @param subEntry
- * @return true if the view is now shown, false if it now hidden
- */
- public boolean toggleSimSelector(final boolean animate, final SubscriptionListEntry subEntry) {
- mSimInput.setSelected(subEntry);
- return mSimInput.toggle(animate);
- }
-
- public boolean updateActionBar(final ActionBar actionBar) {
- for (int i = 0; i < mInputs.length; i++) {
- if (mInputs[i].mShowing) {
- return mInputs[i].updateActionBar(actionBar);
- }
- }
- return false;
- }
-
- @VisibleForTesting
- boolean isMediaPickerVisible() {
- return mMediaInput.mShowing;
- }
-
- @VisibleForTesting
- boolean isSimSelectorVisible() {
- return mSimInput.mShowing;
- }
-
- @VisibleForTesting
- boolean isImeKeyboardVisible() {
- return mImeInput.mShowing;
- }
-
- @VisibleForTesting
- void testNotifyImeStateChanged(final boolean imeOpen) {
- mImeStateObserver.onImeStateChanged(imeOpen);
- }
-
- /**
- * returns true if the state of the visibility was actually changed
- */
- @Override
- public boolean showHideInternal(final ConversationInput target, final boolean show,
- final boolean animate) {
- if (!mConversationDataModel.isBound()) {
- return false;
- }
-
- if (target.mShowing == show) {
- return false;
- }
- beginUpdate();
- boolean success;
- if (!show) {
- success = target.hide(animate);
- } else {
- success = target.show(animate);
- }
-
- if (success) {
- target.onVisibilityChanged(show);
- }
- endUpdate();
- return true;
- }
-
- @Override
- public void handleOnShow(final ConversationInput target) {
- if (!mConversationDataModel.isBound()) {
- return;
- }
- beginUpdate();
-
- // All inputs are mutually exclusive. Showing one will hide everything else.
- // The one exception, is that the keyboard and location media chooser can be open at the
- // time to enable searching within that chooser
- for (int i = 0; i < mInputs.length; i++) {
- final ConversationInput currInput = mInputs[i];
- if (currInput != target) {
- // TODO : If there's more exceptions we will want to make this more
- // generic
- if (currInput instanceof ConversationMediaPicker &&
- target instanceof ConversationImeKeyboard &&
- mMediaInput.getExistingOrCreateMediaPicker() != null &&
- mMediaInput.getExistingOrCreateMediaPicker().canShowIme()) {
- // Allow the keyboard and location mediaPicker to be open at the same time,
- // but ensure the media picker is full screen to allow enough room
- mMediaInput.getExistingOrCreateMediaPicker().setFullScreen(true);
- continue;
- }
- showHideInternal(currInput, false /* show */, false /* animate */);
- }
- }
- // Always dismiss action mode on show.
- mHost.dismissActionMode();
- // Invoking any non-keyboard input UI is treated as starting message compose.
- if (target != mImeInput) {
- mHost.onStartComposeMessage();
- }
- endUpdate();
- }
-
- @Override
- public void beginUpdate() {
- mUpdateCount++;
- }
-
- @Override
- public void endUpdate() {
- Assert.isTrue(mUpdateCount > 0);
- if (--mUpdateCount == 0) {
- // Always try to update the host action bar after every update cycle.
- mHost.invalidateActionBar();
- }
- }
-
- private void updateHostOptionsMenu() {
- mHost.setOptionsMenuVisibility(!mMediaInput.isOpen());
- }
-
- /**
- * Manages showing/hiding the media picker in conversation.
- */
- private class ConversationMediaPicker extends ConversationInput {
- public ConversationMediaPicker(ConversationInputBase baseHost) {
- super(baseHost, false);
- }
-
- private MediaPicker mMediaPicker;
-
- @Override
- public boolean show(boolean animate) {
- if (mMediaPicker == null) {
- mMediaPicker = getExistingOrCreateMediaPicker();
- setConversationThemeColor(ConversationDrawables.get().getConversationThemeColor());
- mMediaPicker.setSubscriptionDataProvider(mHost);
- mMediaPicker.setDraftMessageDataModel(mDraftDataModel);
- mMediaPicker.setListener(new MediaPickerListener() {
- @Override
- public void onOpened() {
- handleStateChange();
- }
-
- @Override
- public void onFullScreenChanged(boolean fullScreen) {
- // When we're full screen, we want to disable accessibility on the
- // ComposeMessageView controls (attach button, message input, sim chooser)
- // that are hiding underneath the action bar.
- mSink.setAccessibility(!fullScreen /*enabled*/);
- handleStateChange();
- }
-
- @Override
- public void onDismissed() {
- // Re-enable accessibility on all controls now that the media picker is
- // going away.
- mSink.setAccessibility(true /*enabled*/);
- handleStateChange();
- }
-
- private void handleStateChange() {
- onVisibilityChanged(isOpen());
- mHost.invalidateActionBar();
- updateHostOptionsMenu();
- }
-
- @Override
- public void onItemsSelected(final Collection<MessagePartData> items,
- final boolean resumeCompose) {
- mSink.onMediaItemsSelected(items);
- mHost.invalidateActionBar();
- if (resumeCompose) {
- mSink.resumeComposeMessage();
- }
- }
-
- @Override
- public void onItemUnselected(final MessagePartData item) {
- mSink.onMediaItemsUnselected(item);
- mHost.invalidateActionBar();
- }
-
- @Override
- public void onConfirmItemSelection() {
- mSink.resumeComposeMessage();
- }
-
- @Override
- public void onPendingItemAdded(final PendingAttachmentData pendingItem) {
- mSink.onPendingAttachmentAdded(pendingItem);
- }
-
- @Override
- public void onChooserSelected(final int chooserIndex) {
- mHost.invalidateActionBar();
- mHost.dismissActionMode();
- }
- });
- }
-
- mMediaPicker.open(MediaPicker.MEDIA_TYPE_DEFAULT, animate);
-
- return isOpen();
- }
-
- @Override
- public boolean hide(boolean animate) {
- if (mMediaPicker != null) {
- mMediaPicker.dismiss(animate);
- }
- return !isOpen();
- }
-
- public void resetViewHolderState() {
- if (mMediaPicker != null) {
- mMediaPicker.resetViewHolderState();
- }
- }
-
- public void setConversationThemeColor(final int themeColor) {
- if (mMediaPicker != null) {
- mMediaPicker.setConversationThemeColor(themeColor);
- }
- }
-
- private boolean isOpen() {
- return (mMediaPicker != null && mMediaPicker.isOpen());
- }
-
- private MediaPicker getExistingOrCreateMediaPicker() {
- if (mMediaPicker != null) {
- return mMediaPicker;
- }
- MediaPicker mediaPicker = (MediaPicker)
- mFragmentManager.findFragmentByTag(MediaPicker.FRAGMENT_TAG);
- if (mediaPicker == null) {
- mediaPicker = mHost.createMediaPicker();
- if (mediaPicker == null) {
- return null; // this use of ComposeMessageView doesn't support media picking
- }
- mFragmentManager.beginTransaction().replace(
- R.id.mediapicker_container,
- mediaPicker,
- MediaPicker.FRAGMENT_TAG).commit();
- }
- return mediaPicker;
- }
-
- @Override
- public boolean updateActionBar(ActionBar actionBar) {
- if (isOpen()) {
- mMediaPicker.updateActionBar(actionBar);
- return true;
- }
- return false;
- }
-
- @Override
- public boolean onNavigationUpPressed() {
- if (isOpen() && mMediaPicker.isFullScreen()) {
- return onBackPressed();
- }
- return super.onNavigationUpPressed();
- }
-
- public boolean onBackPressed() {
- if (mMediaPicker != null && mMediaPicker.onBackPressed()) {
- return true;
- }
- return super.onBackPressed();
- }
- }
-
- /**
- * Manages showing/hiding the SIM selector in conversation.
- */
- private class SimSelector extends ConversationSimSelector {
- public SimSelector(ConversationInputBase baseHost) {
- super(baseHost);
- }
-
- @Override
- protected SimSelectorView getSimSelectorView() {
- return mHost.getSimSelectorView();
- }
-
- @Override
- public int getSimSelectorItemLayoutId() {
- return mHost.getSimSelectorItemLayoutId();
- }
-
- @Override
- protected void selectSim(SubscriptionListEntry item) {
- mHost.selectSim(item);
- }
-
- @Override
- public boolean show(boolean animate) {
- final boolean result = super.show(animate);
- mHost.showHideSimSelector(true /*show*/);
- return result;
- }
-
- @Override
- public boolean hide(boolean animate) {
- final boolean result = super.hide(animate);
- mHost.showHideSimSelector(false /*show*/);
- return result;
- }
- }
-
- /**
- * Manages showing/hiding the IME keyboard in conversation.
- */
- private class ConversationImeKeyboard extends ConversationInput {
- public ConversationImeKeyboard(ConversationInputBase baseHost, final boolean isShowing) {
- super(baseHost, isShowing);
- }
-
- @Override
- public boolean show(boolean animate) {
- ImeUtil.get().showImeKeyboard(mContext, mSink.getComposeEditText());
- return true;
- }
-
- @Override
- public boolean hide(boolean animate) {
- ImeUtil.get().hideImeKeyboard(mContext, mSink.getComposeEditText());
- return true;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java b/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java
deleted file mode 100644
index 2748fff..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationMessageAdapter.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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.content.Context;
-import android.database.Cursor;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.AsyncImageView;
-import com.android.messaging.ui.CursorRecyclerAdapter;
-import com.android.messaging.ui.AsyncImageView.AsyncImageViewDelayLoader;
-import com.android.messaging.ui.conversation.ConversationMessageView.ConversationMessageViewHost;
-import com.android.messaging.util.Assert;
-
-import java.util.HashSet;
-import java.util.List;
-
-/**
- * Provides an interface to expose Conversation Message Cursor data to a UI widget like a
- * RecyclerView.
- */
-public class ConversationMessageAdapter extends
- CursorRecyclerAdapter<ConversationMessageAdapter.ConversationMessageViewHolder> {
-
- private final ConversationMessageViewHost mHost;
- private final AsyncImageViewDelayLoader mImageViewDelayLoader;
- private final View.OnClickListener mViewClickListener;
- private final View.OnLongClickListener mViewLongClickListener;
- private boolean mOneOnOne;
- private String mSelectedMessageId;
-
- public ConversationMessageAdapter(final Context context, final Cursor cursor,
- final ConversationMessageViewHost host,
- final AsyncImageViewDelayLoader imageViewDelayLoader,
- final View.OnClickListener viewClickListener,
- final View.OnLongClickListener longClickListener) {
- super(context, cursor, 0);
- mHost = host;
- mViewClickListener = viewClickListener;
- mViewLongClickListener = longClickListener;
- mImageViewDelayLoader = imageViewDelayLoader;
- setHasStableIds(true);
- }
-
- @Override
- public void bindViewHolder(final ConversationMessageViewHolder holder,
- final Context context, final Cursor cursor) {
- Assert.isTrue(holder.mView instanceof ConversationMessageView);
- final ConversationMessageView conversationMessageView =
- (ConversationMessageView) holder.mView;
- conversationMessageView.bind(cursor, mOneOnOne, mSelectedMessageId);
- }
-
- @Override
- public ConversationMessageViewHolder createViewHolder(final Context context,
- final ViewGroup parent, final int viewType) {
- final LayoutInflater layoutInflater = LayoutInflater.from(context);
- final ConversationMessageView conversationMessageView = (ConversationMessageView)
- layoutInflater.inflate(R.layout.conversation_message_view, null);
- conversationMessageView.setHost(mHost);
- conversationMessageView.setImageViewDelayLoader(mImageViewDelayLoader);
- return new ConversationMessageViewHolder(conversationMessageView,
- mViewClickListener, mViewLongClickListener);
- }
-
- public void setSelectedMessage(final String messageId) {
- mSelectedMessageId = messageId;
- notifyDataSetChanged();
- }
-
- public void setOneOnOne(final boolean oneOnOne, final boolean invalidate) {
- if (mOneOnOne != oneOnOne) {
- mOneOnOne = oneOnOne;
- if (invalidate) {
- notifyDataSetChanged();
- }
- }
- }
-
- /**
- * ViewHolder that holds a ConversationMessageView.
- */
- public static class ConversationMessageViewHolder extends RecyclerView.ViewHolder {
- final View mView;
-
- /**
- * @param viewClickListener a View.OnClickListener that should define the interaction when
- * an item in the RecyclerView is clicked.
- */
- public ConversationMessageViewHolder(final View itemView,
- final View.OnClickListener viewClickListener,
- final View.OnLongClickListener viewLongClickListener) {
- super(itemView);
- mView = itemView;
-
- mView.setOnClickListener(viewClickListener);
- mView.setOnLongClickListener(viewLongClickListener);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java b/src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java
deleted file mode 100644
index ef6aeb4..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationMessageBubbleView.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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.animation.Animator;
-import android.animation.Animator.AnimatorListener;
-import android.animation.ObjectAnimator;
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-
-import com.android.messaging.R;
-import com.android.messaging.annotation.VisibleForAnimation;
-import com.android.messaging.datamodel.data.ConversationMessageBubbleData;
-import com.android.messaging.datamodel.data.ConversationMessageData;
-import com.android.messaging.util.UiUtils;
-
-/**
- * Shows the message bubble for one conversation message. It is able to animate size changes
- * by morphing when the message content changes size.
- */
-// TODO: Move functionality from ConversationMessageView into this class as appropriate
-public class ConversationMessageBubbleView extends LinearLayout {
- private int mIntrinsicWidth;
- private int mMorphedWidth;
- private ObjectAnimator mAnimator;
- private boolean mShouldAnimateWidthChange;
- private final ConversationMessageBubbleData mData;
- private int mRunningStartWidth;
- private ViewGroup mBubbleBackground;
-
- public ConversationMessageBubbleView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mData = new ConversationMessageBubbleData();
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mBubbleBackground = (ViewGroup) findViewById(R.id.message_text_and_info);
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- final int newIntrinsicWidth = getMeasuredWidth();
- if (mIntrinsicWidth == 0 && newIntrinsicWidth != mIntrinsicWidth) {
- if (mShouldAnimateWidthChange) {
- kickOffMorphAnimation(mIntrinsicWidth, newIntrinsicWidth);
- }
- mIntrinsicWidth = newIntrinsicWidth;
- }
-
- if (mMorphedWidth > 0) {
- mBubbleBackground.getLayoutParams().width = mMorphedWidth;
- } else {
- mBubbleBackground.getLayoutParams().width = LayoutParams.WRAP_CONTENT;
- }
- mBubbleBackground.requestLayout();
- }
-
- @VisibleForAnimation
- public void setMorphWidth(final int width) {
- mMorphedWidth = width;
- requestLayout();
- }
-
- public void bind(final ConversationMessageData data) {
- final boolean changed = mData.bind(data);
- // Animate width change only when we are binding to the same message, so that we may
- // animate view size changes on the same message bubble due to things like status text
- // change.
- // Don't animate width change when the bubble contains attachments. Width animation is
- // only suitable for text-only messages (where the bubble size change due to status or
- // time stamp changes).
- mShouldAnimateWidthChange = !changed && !data.hasAttachments();
- if (mAnimator == null) {
- mMorphedWidth = 0;
- }
- }
-
- public void kickOffMorphAnimation(final int oldWidth, final int newWidth) {
- if (mAnimator != null) {
- mAnimator.setIntValues(mRunningStartWidth, newWidth);
- return;
- }
- mRunningStartWidth = oldWidth;
- mAnimator = ObjectAnimator.ofInt(this, "morphWidth", oldWidth, newWidth);
- mAnimator.setDuration(UiUtils.MEDIAPICKER_TRANSITION_DURATION);
- mAnimator.addListener(new AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animator) {
- }
-
- @Override
- public void onAnimationEnd(Animator animator) {
- mAnimator = null;
- mMorphedWidth = 0;
- // Allow the bubble to resize if, for example, the status text changed during
- // the animation. This will snap to the bigger size if needed. This is intentional
- // as animating immediately after looks really bad and switching layout params
- // during the original animation does not achieve the desired effect.
- mBubbleBackground.getLayoutParams().width = LayoutParams.WRAP_CONTENT;
- mBubbleBackground.requestLayout();
- }
-
- @Override
- public void onAnimationCancel(Animator animator) {
- }
-
- @Override
- public void onAnimationRepeat(Animator animator) {
- }
- });
- mAnimator.start();
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationMessageView.java b/src/com/android/messaging/ui/conversation/ConversationMessageView.java
deleted file mode 100644
index e22e2c7..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationMessageView.java
+++ /dev/null
@@ -1,1206 +0,0 @@
-/*
- * 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.content.Context;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.support.annotation.Nullable;
-import android.text.Spanned;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.text.format.Formatter;
-import android.text.style.URLSpan;
-import android.text.util.Linkify;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-import android.widget.ImageView.ScaleType;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.data.ConversationMessageData;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
-import com.android.messaging.datamodel.media.ImageRequestDescriptor;
-import com.android.messaging.datamodel.media.MessagePartImageRequestDescriptor;
-import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.ui.AsyncImageView;
-import com.android.messaging.ui.AsyncImageView.AsyncImageViewDelayLoader;
-import com.android.messaging.ui.AudioAttachmentView;
-import com.android.messaging.ui.ContactIconView;
-import com.android.messaging.ui.ConversationDrawables;
-import com.android.messaging.ui.MultiAttachmentLayout;
-import com.android.messaging.ui.MultiAttachmentLayout.OnAttachmentClickListener;
-import com.android.messaging.ui.PersonItemView;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.VideoThumbnailView;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.UiUtils;
-import com.android.messaging.util.YouTubeUtil;
-import com.google.common.base.Predicate;
-
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * The view for a single entry in a conversation.
- */
-public class ConversationMessageView extends FrameLayout implements View.OnClickListener,
- View.OnLongClickListener, OnAttachmentClickListener {
- public interface ConversationMessageViewHost {
- boolean onAttachmentClick(ConversationMessageView view, MessagePartData attachment,
- Rect imageBounds, boolean longPress);
- SubscriptionListEntry getSubscriptionEntryForSelfParticipant(String selfParticipantId,
- boolean excludeDefault);
- }
-
- private final ConversationMessageData mData;
-
- private LinearLayout mMessageAttachmentsView;
- private MultiAttachmentLayout mMultiAttachmentView;
- private AsyncImageView mMessageImageView;
- private TextView mMessageTextView;
- private boolean mMessageTextHasLinks;
- private boolean mMessageHasYouTubeLink;
- private TextView mStatusTextView;
- private TextView mTitleTextView;
- private TextView mMmsInfoTextView;
- private LinearLayout mMessageTitleLayout;
- private TextView mSenderNameTextView;
- private ContactIconView mContactIconView;
- private ConversationMessageBubbleView mMessageBubble;
- private View mSubjectView;
- private TextView mSubjectLabel;
- private TextView mSubjectText;
- private View mDeliveredBadge;
- private ViewGroup mMessageMetadataView;
- private ViewGroup mMessageTextAndInfoView;
- private TextView mSimNameView;
-
- private boolean mOneOnOne;
- private ConversationMessageViewHost mHost;
-
- public ConversationMessageView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- // TODO: we should switch to using Binding and DataModel factory methods.
- mData = new ConversationMessageData();
- }
-
- @Override
- protected void onFinishInflate() {
- mContactIconView = (ContactIconView) findViewById(R.id.conversation_icon);
- mContactIconView.setOnLongClickListener(new OnLongClickListener() {
- @Override
- public boolean onLongClick(final View view) {
- ConversationMessageView.this.performLongClick();
- return true;
- }
- });
-
- mMessageAttachmentsView = (LinearLayout) findViewById(R.id.message_attachments);
- mMultiAttachmentView = (MultiAttachmentLayout) findViewById(R.id.multiple_attachments);
- mMultiAttachmentView.setOnAttachmentClickListener(this);
-
- mMessageImageView = (AsyncImageView) findViewById(R.id.message_image);
- mMessageImageView.setOnClickListener(this);
- mMessageImageView.setOnLongClickListener(this);
-
- mMessageTextView = (TextView) findViewById(R.id.message_text);
- mMessageTextView.setOnClickListener(this);
- IgnoreLinkLongClickHelper.ignoreLinkLongClick(mMessageTextView, this);
-
- mStatusTextView = (TextView) findViewById(R.id.message_status);
- mTitleTextView = (TextView) findViewById(R.id.message_title);
- mMmsInfoTextView = (TextView) findViewById(R.id.mms_info);
- mMessageTitleLayout = (LinearLayout) findViewById(R.id.message_title_layout);
- mSenderNameTextView = (TextView) findViewById(R.id.message_sender_name);
- mMessageBubble = (ConversationMessageBubbleView) findViewById(R.id.message_content);
- mSubjectView = findViewById(R.id.subject_container);
- mSubjectLabel = (TextView) mSubjectView.findViewById(R.id.subject_label);
- mSubjectText = (TextView) mSubjectView.findViewById(R.id.subject_text);
- mDeliveredBadge = findViewById(R.id.smsDeliveredBadge);
- mMessageMetadataView = (ViewGroup) findViewById(R.id.message_metadata);
- mMessageTextAndInfoView = (ViewGroup) findViewById(R.id.message_text_and_info);
- mSimNameView = (TextView) findViewById(R.id.sim_name);
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- final int horizontalSpace = MeasureSpec.getSize(widthMeasureSpec);
- final int iconSize = getResources()
- .getDimensionPixelSize(R.dimen.conversation_message_contact_icon_size);
-
- final int unspecifiedMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- final int iconMeasureSpec = MeasureSpec.makeMeasureSpec(iconSize, MeasureSpec.EXACTLY);
-
- mContactIconView.measure(iconMeasureSpec, iconMeasureSpec);
-
- final int arrowWidth =
- getResources().getDimensionPixelSize(R.dimen.message_bubble_arrow_width);
-
- // We need to subtract contact icon width twice from the horizontal space to get
- // the max leftover space because we want the message bubble to extend no further than the
- // starting position of the message bubble in the opposite direction.
- final int maxLeftoverSpace = horizontalSpace - mContactIconView.getMeasuredWidth() * 2
- - arrowWidth - getPaddingLeft() - getPaddingRight();
- final int messageContentWidthMeasureSpec = MeasureSpec.makeMeasureSpec(maxLeftoverSpace,
- MeasureSpec.AT_MOST);
-
- mMessageBubble.measure(messageContentWidthMeasureSpec, unspecifiedMeasureSpec);
-
- final int maxHeight = Math.max(mContactIconView.getMeasuredHeight(),
- mMessageBubble.getMeasuredHeight());
- setMeasuredDimension(horizontalSpace, maxHeight + getPaddingBottom() + getPaddingTop());
- }
-
- @Override
- protected void onLayout(final boolean changed, final int left, final int top, final int right,
- final int bottom) {
- final boolean isRtl = AccessibilityUtil.isLayoutRtl(this);
-
- final int iconWidth = mContactIconView.getMeasuredWidth();
- final int iconHeight = mContactIconView.getMeasuredHeight();
- final int iconTop = getPaddingTop();
- final int contentWidth = (right -left) - iconWidth - getPaddingLeft() - getPaddingRight();
- final int contentHeight = mMessageBubble.getMeasuredHeight();
- final int contentTop = iconTop;
-
- final int iconLeft;
- final int contentLeft;
- if (mData.getIsIncoming()) {
- if (isRtl) {
- iconLeft = (right - left) - getPaddingRight() - iconWidth;
- contentLeft = iconLeft - contentWidth;
- } else {
- iconLeft = getPaddingLeft();
- contentLeft = iconLeft + iconWidth;
- }
- } else {
- if (isRtl) {
- iconLeft = getPaddingLeft();
- contentLeft = iconLeft + iconWidth;
- } else {
- iconLeft = (right - left) - getPaddingRight() - iconWidth;
- contentLeft = iconLeft - contentWidth;
- }
- }
-
- mContactIconView.layout(iconLeft, iconTop, iconLeft + iconWidth, iconTop + iconHeight);
-
- mMessageBubble.layout(contentLeft, contentTop, contentLeft + contentWidth,
- contentTop + contentHeight);
- }
-
- /**
- * Fills in the data associated with this view.
- *
- * @param cursor The cursor from a MessageList that this view is in, pointing to its entry.
- */
- public void bind(final Cursor cursor) {
- bind(cursor, true, null);
- }
-
- /**
- * Fills in the data associated with this view.
- *
- * @param cursor The cursor from a MessageList that this view is in, pointing to its entry.
- * @param oneOnOne Whether this is a 1:1 conversation
- */
- public void bind(final Cursor cursor,
- final boolean oneOnOne, final String selectedMessageId) {
- mOneOnOne = oneOnOne;
-
- // Update our UI model
- mData.bind(cursor);
- setSelected(TextUtils.equals(mData.getMessageId(), selectedMessageId));
-
- // Update text and image content for the view.
- updateViewContent();
-
- // Update colors and layout parameters for the view.
- updateViewAppearance();
-
- updateContentDescription();
- }
-
- public void setHost(final ConversationMessageViewHost host) {
- mHost = host;
- }
-
- /**
- * Sets a delay loader instance to manage loading / resuming of image attachments.
- */
- public void setImageViewDelayLoader(final AsyncImageViewDelayLoader delayLoader) {
- Assert.notNull(mMessageImageView);
- mMessageImageView.setDelayLoader(delayLoader);
- mMultiAttachmentView.setImageViewDelayLoader(delayLoader);
- }
-
- public ConversationMessageData getData() {
- return mData;
- }
-
- /**
- * Returns whether we should show simplified visual style for the message view (i.e. hide the
- * avatar and bubble arrow, reduce padding).
- */
- private boolean shouldShowSimplifiedVisualStyle() {
- return mData.getCanClusterWithPreviousMessage();
- }
-
- /**
- * Returns whether we need to show message bubble arrow. We don't show arrow if the message
- * contains media attachments or if shouldShowSimplifiedVisualStyle() is true.
- */
- private boolean shouldShowMessageBubbleArrow() {
- return !shouldShowSimplifiedVisualStyle()
- && !(mData.hasAttachments() || mMessageHasYouTubeLink);
- }
-
- /**
- * Returns whether we need to show a message bubble for text content.
- */
- private boolean shouldShowMessageTextBubble() {
- if (mData.hasText()) {
- return true;
- }
- final String subjectText = MmsUtils.cleanseMmsSubject(getResources(),
- mData.getMmsSubject());
- if (!TextUtils.isEmpty(subjectText)) {
- return true;
- }
- return false;
- }
-
- private void updateViewContent() {
- updateMessageContent();
- int titleResId = -1;
- int statusResId = -1;
- String statusText = null;
- switch(mData.getStatus()) {
- case MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
- case MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
- case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
- case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
- titleResId = R.string.message_title_downloading;
- statusResId = R.string.message_status_downloading;
- break;
-
- case MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD:
- if (!OsUtil.isSecondaryUser()) {
- titleResId = R.string.message_title_manual_download;
- if (isSelected()) {
- statusResId = R.string.message_status_download_action;
- } else {
- statusResId = R.string.message_status_download;
- }
- }
- break;
-
- case MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE:
- if (!OsUtil.isSecondaryUser()) {
- titleResId = R.string.message_title_download_failed;
- statusResId = R.string.message_status_download_error;
- }
- break;
-
- case MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED:
- if (!OsUtil.isSecondaryUser()) {
- titleResId = R.string.message_title_download_failed;
- if (isSelected()) {
- statusResId = R.string.message_status_download_action;
- } else {
- statusResId = R.string.message_status_download;
- }
- }
- break;
-
- case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND:
- case MessageData.BUGLE_STATUS_OUTGOING_SENDING:
- statusResId = R.string.message_status_sending;
- break;
-
- case MessageData.BUGLE_STATUS_OUTGOING_RESENDING:
- case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
- statusResId = R.string.message_status_send_retrying;
- break;
-
- case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
- statusResId = R.string.message_status_send_failed_emergency_number;
- break;
-
- case MessageData.BUGLE_STATUS_OUTGOING_FAILED:
- // don't show the error state unless we're the default sms app
- if (PhoneUtils.getDefault().isDefaultSmsApp()) {
- if (isSelected()) {
- statusResId = R.string.message_status_resend;
- } else {
- statusResId = MmsUtils.mapRawStatusToErrorResourceId(
- mData.getStatus(), mData.getRawTelephonyStatus());
- }
- break;
- }
- // FALL THROUGH HERE
-
- case MessageData.BUGLE_STATUS_OUTGOING_COMPLETE:
- case MessageData.BUGLE_STATUS_INCOMING_COMPLETE:
- default:
- if (!mData.getCanClusterWithNextMessage()) {
- statusText = mData.getFormattedReceivedTimeStamp();
- }
- break;
- }
-
- final boolean titleVisible = (titleResId >= 0);
- if (titleVisible) {
- final String titleText = getResources().getString(titleResId);
- mTitleTextView.setText(titleText);
-
- final String mmsInfoText = getResources().getString(
- R.string.mms_info,
- Formatter.formatFileSize(getContext(), mData.getSmsMessageSize()),
- DateUtils.formatDateTime(
- getContext(),
- mData.getMmsExpiry(),
- DateUtils.FORMAT_SHOW_DATE |
- DateUtils.FORMAT_SHOW_TIME |
- DateUtils.FORMAT_NUMERIC_DATE |
- DateUtils.FORMAT_NO_YEAR));
- mMmsInfoTextView.setText(mmsInfoText);
- mMessageTitleLayout.setVisibility(View.VISIBLE);
- } else {
- mMessageTitleLayout.setVisibility(View.GONE);
- }
-
- final String subjectText = MmsUtils.cleanseMmsSubject(getResources(),
- mData.getMmsSubject());
- final boolean subjectVisible = !TextUtils.isEmpty(subjectText);
-
- final boolean senderNameVisible = !mOneOnOne && !mData.getCanClusterWithNextMessage()
- && mData.getIsIncoming();
- if (senderNameVisible) {
- mSenderNameTextView.setText(mData.getSenderDisplayName());
- mSenderNameTextView.setVisibility(View.VISIBLE);
- } else {
- mSenderNameTextView.setVisibility(View.GONE);
- }
-
- if (statusResId >= 0) {
- statusText = getResources().getString(statusResId);
- }
-
- // We set the text even if the view will be GONE for accessibility
- mStatusTextView.setText(statusText);
- final boolean statusVisible = !TextUtils.isEmpty(statusText);
- if (statusVisible) {
- mStatusTextView.setVisibility(View.VISIBLE);
- } else {
- mStatusTextView.setVisibility(View.GONE);
- }
-
- final boolean deliveredBadgeVisible =
- mData.getStatus() == MessageData.BUGLE_STATUS_OUTGOING_DELIVERED;
- mDeliveredBadge.setVisibility(deliveredBadgeVisible ? View.VISIBLE : View.GONE);
-
- // Update the sim indicator.
- final boolean showSimIconAsIncoming = mData.getIsIncoming() &&
- (!mData.hasAttachments() || shouldShowMessageTextBubble());
- final SubscriptionListEntry subscriptionEntry =
- mHost.getSubscriptionEntryForSelfParticipant(mData.getSelfParticipantId(),
- true /* excludeDefault */);
- final boolean simNameVisible = subscriptionEntry != null &&
- !TextUtils.isEmpty(subscriptionEntry.displayName) &&
- !mData.getCanClusterWithNextMessage();
- if (simNameVisible) {
- final String simNameText = mData.getIsIncoming() ? getResources().getString(
- R.string.incoming_sim_name_text, subscriptionEntry.displayName) :
- subscriptionEntry.displayName;
- mSimNameView.setText(simNameText);
- mSimNameView.setTextColor(showSimIconAsIncoming ? getResources().getColor(
- R.color.timestamp_text_incoming) : subscriptionEntry.displayColor);
- mSimNameView.setVisibility(VISIBLE);
- } else {
- mSimNameView.setText(null);
- mSimNameView.setVisibility(GONE);
- }
-
- final boolean metadataVisible = senderNameVisible || statusVisible
- || deliveredBadgeVisible || simNameVisible;
- mMessageMetadataView.setVisibility(metadataVisible ? View.VISIBLE : View.GONE);
-
- final boolean messageTextAndOrInfoVisible = titleVisible || subjectVisible
- || mData.hasText() || metadataVisible;
- mMessageTextAndInfoView.setVisibility(
- messageTextAndOrInfoVisible ? View.VISIBLE : View.GONE);
-
- if (shouldShowSimplifiedVisualStyle()) {
- mContactIconView.setVisibility(View.GONE);
- mContactIconView.setImageResourceUri(null);
- } else {
- mContactIconView.setVisibility(View.VISIBLE);
- final Uri avatarUri = AvatarUriUtil.createAvatarUri(
- mData.getSenderProfilePhotoUri(),
- mData.getSenderFullName(),
- mData.getSenderNormalizedDestination(),
- mData.getSenderContactLookupKey());
- mContactIconView.setImageResourceUri(avatarUri, mData.getSenderContactId(),
- mData.getSenderContactLookupKey(), mData.getSenderNormalizedDestination());
- }
- }
-
- private void updateMessageContent() {
- // We must update the text before the attachments since we search the text to see if we
- // should make a preview youtube image in the attachments
- updateMessageText();
- updateMessageAttachments();
- updateMessageSubject();
- mMessageBubble.bind(mData);
- }
-
- private void updateMessageAttachments() {
- // Bind video, audio, and VCard attachments. If there are multiple, they stack vertically.
- bindAttachmentsOfSameType(sVideoFilter,
- R.layout.message_video_attachment, mVideoViewBinder, VideoThumbnailView.class);
- bindAttachmentsOfSameType(sAudioFilter,
- R.layout.message_audio_attachment, mAudioViewBinder, AudioAttachmentView.class);
- bindAttachmentsOfSameType(sVCardFilter,
- R.layout.message_vcard_attachment, mVCardViewBinder, PersonItemView.class);
-
- // Bind image attachments. If there are multiple, they are shown in a collage view.
- final List<MessagePartData> imageParts = mData.getAttachments(sImageFilter);
- if (imageParts.size() > 1) {
- Collections.sort(imageParts, sImageComparator);
- mMultiAttachmentView.bindAttachments(imageParts, null, imageParts.size());
- mMultiAttachmentView.setVisibility(View.VISIBLE);
- } else {
- mMultiAttachmentView.setVisibility(View.GONE);
- }
-
- // In the case that we have no image attachments and exactly one youtube link in a message
- // then we will show a preview.
- String youtubeThumbnailUrl = null;
- String originalYoutubeLink = null;
- if (mMessageTextHasLinks && imageParts.size() == 0) {
- CharSequence messageTextWithSpans = mMessageTextView.getText();
- final URLSpan[] spans = ((Spanned) messageTextWithSpans).getSpans(0,
- messageTextWithSpans.length(), URLSpan.class);
- for (URLSpan span : spans) {
- String url = span.getURL();
- String youtubeLinkForUrl = YouTubeUtil.getYoutubePreviewImageLink(url);
- if (!TextUtils.isEmpty(youtubeLinkForUrl)) {
- if (TextUtils.isEmpty(youtubeThumbnailUrl)) {
- // Save the youtube link if we don't already have one
- youtubeThumbnailUrl = youtubeLinkForUrl;
- originalYoutubeLink = url;
- } else {
- // We already have a youtube link. This means we have two youtube links so
- // we shall show none.
- youtubeThumbnailUrl = null;
- originalYoutubeLink = null;
- break;
- }
- }
- }
- }
- // We need to keep track if we have a youtube link in the message so that we will not show
- // the arrow
- mMessageHasYouTubeLink = !TextUtils.isEmpty(youtubeThumbnailUrl);
-
- // We will show the message image view if there is one attachment or one youtube link
- if (imageParts.size() == 1 || mMessageHasYouTubeLink) {
- // Get the display metrics for a hint for how large to pull the image data into
- final WindowManager windowManager = (WindowManager) getContext().
- getSystemService(Context.WINDOW_SERVICE);
- final DisplayMetrics displayMetrics = new DisplayMetrics();
- windowManager.getDefaultDisplay().getMetrics(displayMetrics);
-
- final int iconSize = getResources()
- .getDimensionPixelSize(R.dimen.conversation_message_contact_icon_size);
- final int desiredWidth = displayMetrics.widthPixels - iconSize - iconSize;
-
- if (imageParts.size() == 1) {
- final MessagePartData imagePart = imageParts.get(0);
- // If the image is big, we want to scale it down to save memory since we're going to
- // scale it down to fit into the bubble width. We don't constrain the height.
- final ImageRequestDescriptor imageRequest =
- new MessagePartImageRequestDescriptor(imagePart,
- desiredWidth,
- MessagePartData.UNSPECIFIED_SIZE,
- false);
- adjustImageViewBounds(imagePart);
- mMessageImageView.setImageResourceId(imageRequest);
- mMessageImageView.setTag(imagePart);
- } else {
- // Youtube Thumbnail image
- final ImageRequestDescriptor imageRequest =
- new UriImageRequestDescriptor(Uri.parse(youtubeThumbnailUrl), desiredWidth,
- MessagePartData.UNSPECIFIED_SIZE, true /* allowCompression */,
- true /* isStatic */, false /* cropToCircle */,
- ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
- ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
- mMessageImageView.setImageResourceId(imageRequest);
- mMessageImageView.setTag(originalYoutubeLink);
- }
- mMessageImageView.setVisibility(View.VISIBLE);
- } else {
- mMessageImageView.setImageResourceId(null);
- mMessageImageView.setVisibility(View.GONE);
- }
-
- // Show the message attachments container if any of its children are visible
- boolean attachmentsVisible = false;
- for (int i = 0, size = mMessageAttachmentsView.getChildCount(); i < size; i++) {
- final View attachmentView = mMessageAttachmentsView.getChildAt(i);
- if (attachmentView.getVisibility() == View.VISIBLE) {
- attachmentsVisible = true;
- break;
- }
- }
- mMessageAttachmentsView.setVisibility(attachmentsVisible ? View.VISIBLE : View.GONE);
- }
-
- private void bindAttachmentsOfSameType(final Predicate<MessagePartData> attachmentTypeFilter,
- final int attachmentViewLayoutRes, final AttachmentViewBinder viewBinder,
- final Class<?> attachmentViewClass) {
- final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
-
- // Iterate through all attachments of a particular type (video, audio, etc).
- // Find the first attachment index that matches the given type if possible.
- int attachmentViewIndex = -1;
- View existingAttachmentView;
- do {
- existingAttachmentView = mMessageAttachmentsView.getChildAt(++attachmentViewIndex);
- } while (existingAttachmentView != null &&
- !(attachmentViewClass.isInstance(existingAttachmentView)));
-
- for (final MessagePartData attachment : mData.getAttachments(attachmentTypeFilter)) {
- View attachmentView = mMessageAttachmentsView.getChildAt(attachmentViewIndex);
- if (!attachmentViewClass.isInstance(attachmentView)) {
- attachmentView = layoutInflater.inflate(attachmentViewLayoutRes,
- mMessageAttachmentsView, false /* attachToRoot */);
- attachmentView.setOnClickListener(this);
- attachmentView.setOnLongClickListener(this);
- mMessageAttachmentsView.addView(attachmentView, attachmentViewIndex);
- }
- viewBinder.bindView(attachmentView, attachment);
- attachmentView.setTag(attachment);
- attachmentView.setVisibility(View.VISIBLE);
- attachmentViewIndex++;
- }
- // If there are unused views left over, unbind or remove them.
- while (attachmentViewIndex < mMessageAttachmentsView.getChildCount()) {
- final View attachmentView = mMessageAttachmentsView.getChildAt(attachmentViewIndex);
- if (attachmentViewClass.isInstance(attachmentView)) {
- mMessageAttachmentsView.removeViewAt(attachmentViewIndex);
- } else {
- // No more views of this type; we're done.
- break;
- }
- }
- }
-
- private void updateMessageSubject() {
- final String subjectText = MmsUtils.cleanseMmsSubject(getResources(),
- mData.getMmsSubject());
- final boolean subjectVisible = !TextUtils.isEmpty(subjectText);
-
- if (subjectVisible) {
- mSubjectText.setText(subjectText);
- mSubjectView.setVisibility(View.VISIBLE);
- } else {
- mSubjectView.setVisibility(View.GONE);
- }
- }
-
- private void updateMessageText() {
- final String text = mData.getText();
- if (!TextUtils.isEmpty(text)) {
- mMessageTextView.setText(text);
- // Linkify phone numbers, web urls, emails, and map addresses to allow users to
- // click on them and take the default intent.
- mMessageTextHasLinks = Linkify.addLinks(mMessageTextView, Linkify.ALL);
- mMessageTextView.setVisibility(View.VISIBLE);
- } else {
- mMessageTextView.setVisibility(View.GONE);
- mMessageTextHasLinks = false;
- }
- }
-
- private void updateViewAppearance() {
- final Resources res = getResources();
- final ConversationDrawables drawableProvider = ConversationDrawables.get();
- final boolean incoming = mData.getIsIncoming();
- final boolean outgoing = !incoming;
- final boolean showArrow = shouldShowMessageBubbleArrow();
-
- final int messageTopPaddingClustered =
- res.getDimensionPixelSize(R.dimen.message_padding_same_author);
- final int messageTopPaddingDefault =
- res.getDimensionPixelSize(R.dimen.message_padding_default);
- final int arrowWidth = res.getDimensionPixelOffset(R.dimen.message_bubble_arrow_width);
- final int messageTextMinHeightDefault = res.getDimensionPixelSize(
- R.dimen.conversation_message_contact_icon_size);
- final int messageTextLeftRightPadding = res.getDimensionPixelOffset(
- R.dimen.message_text_left_right_padding);
- final int textTopPaddingDefault = res.getDimensionPixelOffset(
- R.dimen.message_text_top_padding);
- final int textBottomPaddingDefault = res.getDimensionPixelOffset(
- R.dimen.message_text_bottom_padding);
-
- // These values depend on whether the message has text, attachments, or both.
- // We intentionally don't set defaults, so the compiler will tell us if we forget
- // to set one of them, or if we set one more than once.
- final int contentLeftPadding, contentRightPadding;
- final Drawable textBackground;
- final int textMinHeight;
- final int textTopMargin;
- final int textTopPadding, textBottomPadding;
- final int textLeftPadding, textRightPadding;
-
- if (mData.hasAttachments()) {
- if (shouldShowMessageTextBubble()) {
- // Text and attachment(s)
- contentLeftPadding = incoming ? arrowWidth : 0;
- contentRightPadding = outgoing ? arrowWidth : 0;
- textBackground = drawableProvider.getBubbleDrawable(
- isSelected(),
- incoming,
- false /* needArrow */,
- mData.hasIncomingErrorStatus());
- textMinHeight = messageTextMinHeightDefault;
- textTopMargin = messageTopPaddingClustered;
- textTopPadding = textTopPaddingDefault;
- textBottomPadding = textBottomPaddingDefault;
- textLeftPadding = messageTextLeftRightPadding;
- textRightPadding = messageTextLeftRightPadding;
- } else {
- // Attachment(s) only
- contentLeftPadding = incoming ? arrowWidth : 0;
- contentRightPadding = outgoing ? arrowWidth : 0;
- textBackground = null;
- textMinHeight = 0;
- textTopMargin = 0;
- textTopPadding = 0;
- textBottomPadding = 0;
- textLeftPadding = 0;
- textRightPadding = 0;
- }
- } else {
- // Text only
- contentLeftPadding = (!showArrow && incoming) ? arrowWidth : 0;
- contentRightPadding = (!showArrow && outgoing) ? arrowWidth : 0;
- textBackground = drawableProvider.getBubbleDrawable(
- isSelected(),
- incoming,
- shouldShowMessageBubbleArrow(),
- mData.hasIncomingErrorStatus());
- textMinHeight = messageTextMinHeightDefault;
- textTopMargin = 0;
- textTopPadding = textTopPaddingDefault;
- textBottomPadding = textBottomPaddingDefault;
- if (showArrow && incoming) {
- textLeftPadding = messageTextLeftRightPadding + arrowWidth;
- } else {
- textLeftPadding = messageTextLeftRightPadding;
- }
- if (showArrow && outgoing) {
- textRightPadding = messageTextLeftRightPadding + arrowWidth;
- } else {
- textRightPadding = messageTextLeftRightPadding;
- }
- }
-
- // These values do not depend on whether the message includes attachments
- final int gravity = incoming ? (Gravity.START | Gravity.CENTER_VERTICAL) :
- (Gravity.END | Gravity.CENTER_VERTICAL);
- final int messageTopPadding = shouldShowSimplifiedVisualStyle() ?
- messageTopPaddingClustered : messageTopPaddingDefault;
- final int metadataTopPadding = res.getDimensionPixelOffset(
- R.dimen.message_metadata_top_padding);
-
- // Update the message text/info views
- ImageUtils.setBackgroundDrawableOnView(mMessageTextAndInfoView, textBackground);
- mMessageTextAndInfoView.setMinimumHeight(textMinHeight);
- final LinearLayout.LayoutParams textAndInfoLayoutParams =
- (LinearLayout.LayoutParams) mMessageTextAndInfoView.getLayoutParams();
- textAndInfoLayoutParams.topMargin = textTopMargin;
-
- if (UiUtils.isRtlMode()) {
- // Need to switch right and left padding in RtL mode
- mMessageTextAndInfoView.setPadding(textRightPadding, textTopPadding, textLeftPadding,
- textBottomPadding);
- mMessageBubble.setPadding(contentRightPadding, 0, contentLeftPadding, 0);
- } else {
- mMessageTextAndInfoView.setPadding(textLeftPadding, textTopPadding, textRightPadding,
- textBottomPadding);
- mMessageBubble.setPadding(contentLeftPadding, 0, contentRightPadding, 0);
- }
-
- // Update the message row and message bubble views
- setPadding(getPaddingLeft(), messageTopPadding, getPaddingRight(), 0);
- mMessageBubble.setGravity(gravity);
- updateMessageAttachmentsAppearance(gravity);
-
- mMessageMetadataView.setPadding(0, metadataTopPadding, 0, 0);
-
- updateTextAppearance();
-
- requestLayout();
- }
-
- private void updateContentDescription() {
- StringBuilder description = new StringBuilder();
-
- Resources res = getResources();
- String separator = res.getString(R.string.enumeration_comma);
-
- // Sender information
- boolean hasPlainTextMessage = !(TextUtils.isEmpty(mData.getText()) ||
- mMessageTextHasLinks);
- if (mData.getIsIncoming()) {
- int senderResId = hasPlainTextMessage
- ? R.string.incoming_text_sender_content_description
- : R.string.incoming_sender_content_description;
- description.append(res.getString(senderResId, mData.getSenderDisplayName()));
- } else {
- int senderResId = hasPlainTextMessage
- ? R.string.outgoing_text_sender_content_description
- : R.string.outgoing_sender_content_description;
- description.append(res.getString(senderResId));
- }
-
- if (mSubjectView.getVisibility() == View.VISIBLE) {
- description.append(separator);
- description.append(mSubjectText.getText());
- }
-
- if (mMessageTextView.getVisibility() == View.VISIBLE) {
- // If the message has hyperlinks, we will let the user navigate to the text message so
- // that the hyperlink can be clicked. Otherwise, the text message does not need to
- // be reachable.
- if (mMessageTextHasLinks) {
- mMessageTextView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- } else {
- mMessageTextView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
- description.append(separator);
- description.append(mMessageTextView.getText());
- }
- }
-
- if (mMessageTitleLayout.getVisibility() == View.VISIBLE) {
- description.append(separator);
- description.append(mTitleTextView.getText());
-
- description.append(separator);
- description.append(mMmsInfoTextView.getText());
- }
-
- if (mStatusTextView.getVisibility() == View.VISIBLE) {
- description.append(separator);
- description.append(mStatusTextView.getText());
- }
-
- if (mSimNameView.getVisibility() == View.VISIBLE) {
- description.append(separator);
- description.append(mSimNameView.getText());
- }
-
- if (mDeliveredBadge.getVisibility() == View.VISIBLE) {
- description.append(separator);
- description.append(res.getString(R.string.delivered_status_content_description));
- }
-
- setContentDescription(description);
- }
-
- private void updateMessageAttachmentsAppearance(final int gravity) {
- mMessageAttachmentsView.setGravity(gravity);
-
- // Tint image/video attachments when selected
- final int selectedImageTint = getResources().getColor(R.color.message_image_selected_tint);
- if (mMessageImageView.getVisibility() == View.VISIBLE) {
- if (isSelected()) {
- mMessageImageView.setColorFilter(selectedImageTint);
- } else {
- mMessageImageView.clearColorFilter();
- }
- }
- if (mMultiAttachmentView.getVisibility() == View.VISIBLE) {
- if (isSelected()) {
- mMultiAttachmentView.setColorFilter(selectedImageTint);
- } else {
- mMultiAttachmentView.clearColorFilter();
- }
- }
- for (int i = 0, size = mMessageAttachmentsView.getChildCount(); i < size; i++) {
- final View attachmentView = mMessageAttachmentsView.getChildAt(i);
- if (attachmentView instanceof VideoThumbnailView
- && attachmentView.getVisibility() == View.VISIBLE) {
- final VideoThumbnailView videoView = (VideoThumbnailView) attachmentView;
- if (isSelected()) {
- videoView.setColorFilter(selectedImageTint);
- } else {
- videoView.clearColorFilter();
- }
- }
- }
-
- // If there are multiple attachment bubbles in a single message, add some separation.
- final int multipleAttachmentPadding =
- getResources().getDimensionPixelSize(R.dimen.message_padding_same_author);
-
- boolean previousVisibleView = false;
- for (int i = 0, size = mMessageAttachmentsView.getChildCount(); i < size; i++) {
- final View attachmentView = mMessageAttachmentsView.getChildAt(i);
- if (attachmentView.getVisibility() == View.VISIBLE) {
- final int margin = previousVisibleView ? multipleAttachmentPadding : 0;
- ((LinearLayout.LayoutParams) attachmentView.getLayoutParams()).topMargin = margin;
- // updateViewAppearance calls requestLayout() at the end, so we don't need to here
- previousVisibleView = true;
- }
- }
- }
-
- private void updateTextAppearance() {
- int messageColorResId;
- int statusColorResId = -1;
- int infoColorResId = -1;
- int timestampColorResId;
- int subjectLabelColorResId;
- if (isSelected()) {
- messageColorResId = R.color.message_text_color_incoming;
- statusColorResId = R.color.message_action_status_text;
- infoColorResId = R.color.message_action_info_text;
- if (shouldShowMessageTextBubble()) {
- timestampColorResId = R.color.message_action_timestamp_text;
- subjectLabelColorResId = R.color.message_action_timestamp_text;
- } else {
- // If there's no text, the timestamp will be shown below the attachments,
- // against the conversation view background.
- timestampColorResId = R.color.timestamp_text_outgoing;
- subjectLabelColorResId = R.color.timestamp_text_outgoing;
- }
- } else {
- messageColorResId = (mData.getIsIncoming() ?
- R.color.message_text_color_incoming : R.color.message_text_color_outgoing);
- statusColorResId = messageColorResId;
- infoColorResId = R.color.timestamp_text_incoming;
- switch(mData.getStatus()) {
-
- case MessageData.BUGLE_STATUS_OUTGOING_FAILED:
- case MessageData.BUGLE_STATUS_OUTGOING_FAILED_EMERGENCY_NUMBER:
- timestampColorResId = R.color.message_failed_timestamp_text;
- subjectLabelColorResId = R.color.timestamp_text_outgoing;
- break;
-
- case MessageData.BUGLE_STATUS_OUTGOING_YET_TO_SEND:
- case MessageData.BUGLE_STATUS_OUTGOING_SENDING:
- case MessageData.BUGLE_STATUS_OUTGOING_RESENDING:
- case MessageData.BUGLE_STATUS_OUTGOING_AWAITING_RETRY:
- case MessageData.BUGLE_STATUS_OUTGOING_COMPLETE:
- case MessageData.BUGLE_STATUS_OUTGOING_DELIVERED:
- timestampColorResId = R.color.timestamp_text_outgoing;
- subjectLabelColorResId = R.color.timestamp_text_outgoing;
- break;
-
- case MessageData.BUGLE_STATUS_INCOMING_EXPIRED_OR_NOT_AVAILABLE:
- case MessageData.BUGLE_STATUS_INCOMING_DOWNLOAD_FAILED:
- messageColorResId = R.color.message_text_color_incoming_download_failed;
- timestampColorResId = R.color.message_download_failed_timestamp_text;
- subjectLabelColorResId = R.color.message_text_color_incoming_download_failed;
- statusColorResId = R.color.message_download_failed_status_text;
- infoColorResId = R.color.message_info_text_incoming_download_failed;
- break;
-
- case MessageData.BUGLE_STATUS_INCOMING_AUTO_DOWNLOADING:
- case MessageData.BUGLE_STATUS_INCOMING_MANUAL_DOWNLOADING:
- case MessageData.BUGLE_STATUS_INCOMING_RETRYING_AUTO_DOWNLOAD:
- case MessageData.BUGLE_STATUS_INCOMING_RETRYING_MANUAL_DOWNLOAD:
- case MessageData.BUGLE_STATUS_INCOMING_YET_TO_MANUAL_DOWNLOAD:
- timestampColorResId = R.color.message_text_color_incoming;
- subjectLabelColorResId = R.color.message_text_color_incoming;
- infoColorResId = R.color.timestamp_text_incoming;
- break;
-
- case MessageData.BUGLE_STATUS_INCOMING_COMPLETE:
- default:
- timestampColorResId = R.color.timestamp_text_incoming;
- subjectLabelColorResId = R.color.timestamp_text_incoming;
- infoColorResId = -1; // Not used
- break;
- }
- }
- final int messageColor = getResources().getColor(messageColorResId);
- mMessageTextView.setTextColor(messageColor);
- mMessageTextView.setLinkTextColor(messageColor);
- mSubjectText.setTextColor(messageColor);
- if (statusColorResId >= 0) {
- mTitleTextView.setTextColor(getResources().getColor(statusColorResId));
- }
- if (infoColorResId >= 0) {
- mMmsInfoTextView.setTextColor(getResources().getColor(infoColorResId));
- }
- if (timestampColorResId == R.color.timestamp_text_incoming &&
- mData.hasAttachments() && !shouldShowMessageTextBubble()) {
- timestampColorResId = R.color.timestamp_text_outgoing;
- }
- mStatusTextView.setTextColor(getResources().getColor(timestampColorResId));
-
- mSubjectLabel.setTextColor(getResources().getColor(subjectLabelColorResId));
- mSenderNameTextView.setTextColor(getResources().getColor(timestampColorResId));
- }
-
- /**
- * If we don't know the size of the image, we want to show it in a fixed-sized frame to
- * avoid janks when the image is loaded and resized. Otherwise, we can set the imageview to
- * take on normal layout params.
- */
- private void adjustImageViewBounds(final MessagePartData imageAttachment) {
- Assert.isTrue(ContentType.isImageType(imageAttachment.getContentType()));
- final ViewGroup.LayoutParams layoutParams = mMessageImageView.getLayoutParams();
- if (imageAttachment.getWidth() == MessagePartData.UNSPECIFIED_SIZE ||
- imageAttachment.getHeight() == MessagePartData.UNSPECIFIED_SIZE) {
- // We don't know the size of the image attachment, enable letterboxing on the image
- // and show a fixed sized attachment. This should happen at most once per image since
- // after the image is loaded we then save the image dimensions to the db so that the
- // next time we can display the full size.
- layoutParams.width = getResources()
- .getDimensionPixelSize(R.dimen.image_attachment_fallback_width);
- layoutParams.height = getResources()
- .getDimensionPixelSize(R.dimen.image_attachment_fallback_height);
- mMessageImageView.setScaleType(ScaleType.CENTER_CROP);
- } else {
- layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
- layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
- // ScaleType.CENTER_INSIDE and FIT_CENTER behave similarly for most images. However,
- // FIT_CENTER works better for small images as it enlarges the image such that the
- // minimum size ("android:minWidth" etc) is honored.
- mMessageImageView.setScaleType(ScaleType.FIT_CENTER);
- }
- }
-
- @Override
- public void onClick(final View view) {
- final Object tag = view.getTag();
- if (tag instanceof MessagePartData) {
- final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(view);
- onAttachmentClick((MessagePartData) tag, bounds, false /* longPress */);
- } else if (tag instanceof String) {
- // Currently the only object that would make a tag of a string is a youtube preview
- // image
- UIIntents.get().launchBrowserForUrl(getContext(), (String) tag);
- }
- }
-
- @Override
- public boolean onLongClick(final View view) {
- if (view == mMessageTextView) {
- // Preemptively handle the long click event on message text so it's not handled by
- // the link spans.
- return performLongClick();
- }
-
- final Object tag = view.getTag();
- if (tag instanceof MessagePartData) {
- final Rect bounds = UiUtils.getMeasuredBoundsOnScreen(view);
- return onAttachmentClick((MessagePartData) tag, bounds, true /* longPress */);
- }
-
- return false;
- }
-
- @Override
- public boolean onAttachmentClick(final MessagePartData attachment,
- final Rect viewBoundsOnScreen, final boolean longPress) {
- return mHost.onAttachmentClick(this, attachment, viewBoundsOnScreen, longPress);
- }
-
- public ContactIconView getContactIconView() {
- return mContactIconView;
- }
-
- // Sort photos in MultiAttachLayout in the same order as the ConversationImagePartsView
- static final Comparator<MessagePartData> sImageComparator = new Comparator<MessagePartData>(){
- @Override
- public int compare(final MessagePartData x, final MessagePartData y) {
- return x.getPartId().compareTo(y.getPartId());
- }
- };
-
- static final Predicate<MessagePartData> sVideoFilter = new Predicate<MessagePartData>() {
- @Override
- public boolean apply(final MessagePartData part) {
- return part.isVideo();
- }
- };
-
- static final Predicate<MessagePartData> sAudioFilter = new Predicate<MessagePartData>() {
- @Override
- public boolean apply(final MessagePartData part) {
- return part.isAudio();
- }
- };
-
- static final Predicate<MessagePartData> sVCardFilter = new Predicate<MessagePartData>() {
- @Override
- public boolean apply(final MessagePartData part) {
- return part.isVCard();
- }
- };
-
- static final Predicate<MessagePartData> sImageFilter = new Predicate<MessagePartData>() {
- @Override
- public boolean apply(final MessagePartData part) {
- return part.isImage();
- }
- };
-
- interface AttachmentViewBinder {
- void bindView(View view, MessagePartData attachment);
- void unbind(View view);
- }
-
- final AttachmentViewBinder mVideoViewBinder = new AttachmentViewBinder() {
- @Override
- public void bindView(final View view, final MessagePartData attachment) {
- ((VideoThumbnailView) view).setSource(attachment, mData.getIsIncoming());
- }
-
- @Override
- public void unbind(final View view) {
- ((VideoThumbnailView) view).setSource((Uri) null, mData.getIsIncoming());
- }
- };
-
- final AttachmentViewBinder mAudioViewBinder = new AttachmentViewBinder() {
- @Override
- public void bindView(final View view, final MessagePartData attachment) {
- final AudioAttachmentView audioView = (AudioAttachmentView) view;
- audioView.bindMessagePartData(attachment, isSelected() || mData.getIsIncoming());
- audioView.setBackground(ConversationDrawables.get().getBubbleDrawable(
- isSelected(), mData.getIsIncoming(), false /* needArrow */,
- mData.hasIncomingErrorStatus()));
- }
-
- @Override
- public void unbind(final View view) {
- ((AudioAttachmentView) view).bindMessagePartData(null, mData.getIsIncoming());
- }
- };
-
- final AttachmentViewBinder mVCardViewBinder = new AttachmentViewBinder() {
- @Override
- public void bindView(final View view, final MessagePartData attachment) {
- final PersonItemView personView = (PersonItemView) view;
- personView.bind(DataModel.get().createVCardContactItemData(getContext(),
- attachment));
- personView.setBackground(ConversationDrawables.get().getBubbleDrawable(
- isSelected(), mData.getIsIncoming(), false /* needArrow */,
- mData.hasIncomingErrorStatus()));
- final int nameTextColorRes;
- final int detailsTextColorRes;
- if (isSelected()) {
- nameTextColorRes = R.color.message_text_color_incoming;
- detailsTextColorRes = R.color.message_text_color_incoming;
- } else {
- nameTextColorRes = mData.getIsIncoming() ? R.color.message_text_color_incoming
- : R.color.message_text_color_outgoing;
- detailsTextColorRes = mData.getIsIncoming() ? R.color.timestamp_text_incoming
- : R.color.timestamp_text_outgoing;
- }
- personView.setNameTextColor(getResources().getColor(nameTextColorRes));
- personView.setDetailsTextColor(getResources().getColor(detailsTextColorRes));
- }
-
- @Override
- public void unbind(final View view) {
- ((PersonItemView) view).bind(null);
- }
- };
-
- /**
- * A helper class that allows us to handle long clicks on linkified message text view (i.e. to
- * select the message) so it's not handled by the link spans to launch apps for the links.
- */
- private static class IgnoreLinkLongClickHelper implements OnLongClickListener, OnTouchListener {
- private boolean mIsLongClick;
- private final OnLongClickListener mDelegateLongClickListener;
-
- /**
- * Ignore long clicks on linkified texts for a given text view.
- * @param textView the TextView to ignore long clicks on
- * @param longClickListener a delegate OnLongClickListener to be called when the view is
- * long clicked.
- */
- public static void ignoreLinkLongClick(final TextView textView,
- @Nullable final OnLongClickListener longClickListener) {
- final IgnoreLinkLongClickHelper helper =
- new IgnoreLinkLongClickHelper(longClickListener);
- textView.setOnLongClickListener(helper);
- textView.setOnTouchListener(helper);
- }
-
- private IgnoreLinkLongClickHelper(@Nullable final OnLongClickListener longClickListener) {
- mDelegateLongClickListener = longClickListener;
- }
-
- @Override
- public boolean onLongClick(final View v) {
- // Record that this click is a long click.
- mIsLongClick = true;
- if (mDelegateLongClickListener != null) {
- return mDelegateLongClickListener.onLongClick(v);
- }
- return false;
- }
-
- @Override
- public boolean onTouch(final View v, final MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_UP && mIsLongClick) {
- // This touch event is a long click, preemptively handle this touch event so that
- // the link span won't get a onClicked() callback.
- mIsLongClick = false;
- return true;
- }
-
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mIsLongClick = false;
- }
- return false;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/ConversationSimSelector.java b/src/com/android/messaging/ui/conversation/ConversationSimSelector.java
deleted file mode 100644
index fc43a46..0000000
--- a/src/com/android/messaging/ui/conversation/ConversationSimSelector.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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.content.Context;
-import android.support.v4.util.Pair;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.SubscriptionListData;
-import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
-import com.android.messaging.ui.conversation.SimSelectorView.SimSelectorViewListener;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.ThreadUtil;
-
-/**
- * Manages showing/hiding the SIM selector in conversation.
- */
-abstract class ConversationSimSelector extends ConversationInput {
- private SimSelectorView mSimSelectorView;
- private Pair<Boolean /* show */, Boolean /* animate */> mPendingShow;
- private boolean mDataReady;
- private String mSelectedSimText;
-
- public ConversationSimSelector(ConversationInputBase baseHost) {
- super(baseHost, false);
- }
-
- public void onSubscriptionListDataLoaded(final SubscriptionListData subscriptionListData) {
- ensureSimSelectorView();
- mSimSelectorView.bind(subscriptionListData);
- mDataReady = subscriptionListData != null && subscriptionListData.hasData();
- if (mPendingShow != null && mDataReady) {
- Assert.isTrue(OsUtil.isAtLeastL_MR1());
- final boolean show = mPendingShow.first;
- final boolean animate = mPendingShow.second;
- ThreadUtil.getMainThreadHandler().post(new Runnable() {
- @Override
- public void run() {
- // This will No-Op if we are no longer attached to the host.
- mConversationInputBase.showHideInternal(ConversationSimSelector.this,
- show, animate);
- }
- });
- mPendingShow = null;
- }
- }
-
- private void announcedSelectedSim() {
- final Context context = Factory.get().getApplicationContext();
- if (AccessibilityUtil.isTouchExplorationEnabled(context) &&
- !TextUtils.isEmpty(mSelectedSimText)) {
- AccessibilityUtil.announceForAccessibilityCompat(
- mSimSelectorView, null,
- context.getString(R.string.selected_sim_content_message, mSelectedSimText));
- }
- }
-
- public void setSelected(final SubscriptionListEntry subEntry) {
- mSelectedSimText = subEntry == null ? null : subEntry.displayName;
- }
-
- @Override
- public boolean show(boolean animate) {
- announcedSelectedSim();
- return showHide(true, animate);
- }
-
- @Override
- public boolean hide(boolean animate) {
- return showHide(false, animate);
- }
-
- private boolean showHide(final boolean show, final boolean animate) {
- if (!OsUtil.isAtLeastL_MR1()) {
- return false;
- }
-
- if (mDataReady) {
- mSimSelectorView.showOrHide(show, animate);
- return mSimSelectorView.isOpen() == show;
- } else {
- mPendingShow = Pair.create(show, animate);
- return false;
- }
- }
-
- private void ensureSimSelectorView() {
- if (mSimSelectorView == null) {
- // Grab the SIM selector view from the host. This class assumes ownership of it.
- mSimSelectorView = getSimSelectorView();
- mSimSelectorView.setItemLayoutId(getSimSelectorItemLayoutId());
- mSimSelectorView.setListener(new SimSelectorViewListener() {
-
- @Override
- public void onSimSelectorVisibilityChanged(boolean visible) {
- onVisibilityChanged(visible);
- }
-
- @Override
- public void onSimItemClicked(SubscriptionListEntry item) {
- selectSim(item);
- }
- });
- }
- }
-
- protected abstract SimSelectorView getSimSelectorView();
- protected abstract void selectSim(final SubscriptionListEntry item);
- protected abstract int getSimSelectorItemLayoutId();
-
-}
diff --git a/src/com/android/messaging/ui/conversation/EnterSelfPhoneNumberDialog.java b/src/com/android/messaging/ui/conversation/EnterSelfPhoneNumberDialog.java
deleted file mode 100644
index e3ad601..0000000
--- a/src/com/android/messaging/ui/conversation/EnterSelfPhoneNumberDialog.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.view.LayoutInflater;
-import android.widget.EditText;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.ParticipantRefresh;
-import com.android.messaging.util.BuglePrefs;
-import com.android.messaging.util.UiUtils;
-
-/**
- * The dialog for the user to enter the phone number of their sim.
- */
-public class EnterSelfPhoneNumberDialog extends DialogFragment {
- private EditText mEditText;
- private int mSubId;
-
- public static EnterSelfPhoneNumberDialog newInstance(final int subId) {
- final EnterSelfPhoneNumberDialog dialog = new EnterSelfPhoneNumberDialog();
- dialog.mSubId = subId;
- return dialog;
- }
-
- @Override
- public Dialog onCreateDialog(final Bundle savedInstanceState) {
- final Context context = getActivity();
- final LayoutInflater inflater = LayoutInflater.from(context);
- mEditText = (EditText) inflater.inflate(R.layout.enter_phone_number_view, null, false);
-
- final AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(R.string.enter_phone_number_title)
- .setMessage(R.string.enter_phone_number_text)
- .setView(mEditText)
- .setNegativeButton(android.R.string.cancel,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog,
- final int button) {
- dismiss();
- }
- })
- .setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog,
- final int button) {
- final String newNumber = mEditText.getText().toString();
- dismiss();
- if (!TextUtils.isEmpty(newNumber)) {
- savePhoneNumberInPrefs(newNumber);
- // TODO: Remove this toast and just auto-send
- // the message instead
- UiUtils.showToast(
- R.string
- .toast_after_setting_default_sms_app_for_message_send);
- }
- }
- });
- return builder.create();
- }
-
- private void savePhoneNumberInPrefs(final String newPhoneNumber) {
- final BuglePrefs subPrefs = BuglePrefs.getSubscriptionPrefs(mSubId);
- subPrefs.putString(getString(R.string.mms_phone_number_pref_key),
- newPhoneNumber);
- // Update the self participants so the new phone number will be reflected
- // everywhere in the UI.
- ParticipantRefresh.refreshSelfParticipants();
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/LaunchConversationActivity.java b/src/com/android/messaging/ui/conversation/LaunchConversationActivity.java
deleted file mode 100644
index 8af9f75..0000000
--- a/src/com/android/messaging/ui/conversation/LaunchConversationActivity.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * 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.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.LaunchConversationData;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.UiUtils;
-import com.android.messaging.util.UriUtil;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLDecoder;
-
-/**
- * Launches ConversationActivity for sending a message to, or viewing messages from, a specific
- * recipient.
- * <p>
- * (This activity should be marked noHistory="true" in AndroidManifest.xml)
- */
-public class LaunchConversationActivity extends Activity implements
- LaunchConversationData.LaunchConversationDataListener {
- static final String SMS_BODY = "sms_body";
- static final String ADDRESS = "address";
- final Binding<LaunchConversationData> mBinding = BindingBase.createBinding(this);
- String mSmsBody;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (UiUtils.redirectToPermissionCheckIfNeeded(this)) {
- return;
- }
-
- final Intent intent = getIntent();
- final String action = intent.getAction();
- if (Intent.ACTION_SENDTO.equals(action) || Intent.ACTION_VIEW.equals(action)) {
- String[] recipients = UriUtil.parseRecipientsFromSmsMmsUri(intent.getData());
- final boolean haveAddress = !TextUtils.isEmpty(intent.getStringExtra(ADDRESS));
- final boolean haveEmail = !TextUtils.isEmpty(intent.getStringExtra(Intent.EXTRA_EMAIL));
- if (recipients == null && (haveAddress || haveEmail)) {
- if (haveAddress) {
- recipients = new String[] { intent.getStringExtra(ADDRESS) };
- } else {
- recipients = new String[] { intent.getStringExtra(Intent.EXTRA_EMAIL) };
- }
- }
- mSmsBody = intent.getStringExtra(SMS_BODY);
- if (TextUtils.isEmpty(mSmsBody)) {
- // Used by intents sent from the web YouTube (and perhaps others).
- mSmsBody = getBody(intent.getData());
- if (TextUtils.isEmpty(mSmsBody)) {
- // If that fails, try yet another method apps use to share text
- if (ContentType.TEXT_PLAIN.equals(intent.getType())) {
- mSmsBody = intent.getStringExtra(Intent.EXTRA_TEXT);
- }
- }
- }
- if (recipients != null) {
- mBinding.bind(DataModel.get().createLaunchConversationData(this));
- mBinding.getData().getOrCreateConversation(mBinding, recipients);
- } else {
- // No recipients were specified in the intent.
- // Start a new conversation with contact picker. The new conversation will be
- // primed with the (optional) message in mSmsBody.
- onGetOrCreateNewConversation(null);
- }
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG, "Unsupported conversation intent action : " + action);
- }
- // As of M, activities without a visible window must finish before onResume completes.
- finish();
- }
-
- private String getBody(final Uri uri) {
- if (uri == null) {
- return null;
- }
- String urlStr = uri.getSchemeSpecificPart();
- if (!urlStr.contains("?")) {
- return null;
- }
- urlStr = urlStr.substring(urlStr.indexOf('?') + 1);
- final String[] params = urlStr.split("&");
- for (final String p : params) {
- if (p.startsWith("body=")) {
- try {
- return URLDecoder.decode(p.substring(5), "UTF-8");
- } catch (final UnsupportedEncodingException e) {
- // Invalid URL, ignore
- }
- }
- }
- return null;
- }
-
- @Override
- public void onGetOrCreateNewConversation(final String conversationId) {
- final Context context = Factory.get().getApplicationContext();
- UIIntents.get().launchConversationActivityWithParentStack(context, conversationId,
- mSmsBody);
- }
-
- @Override
- public void onGetOrCreateNewConversationFailed() {
- UiUtils.showToastAtBottom(R.string.conversation_creation_failure);
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/MessageBubbleBackground.java b/src/com/android/messaging/ui/conversation/MessageBubbleBackground.java
deleted file mode 100644
index 4c22970..0000000
--- a/src/com/android/messaging/ui/conversation/MessageBubbleBackground.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.content.Context;
-import android.util.AttributeSet;
-import android.widget.LinearLayout;
-
-import com.android.messaging.R;
-
-public class MessageBubbleBackground extends LinearLayout {
- private final int mSnapWidthPixels;
-
- public MessageBubbleBackground(Context context, AttributeSet attrs) {
- super(context, attrs);
- mSnapWidthPixels = context.getResources().getDimensionPixelSize(
- R.dimen.conversation_bubble_width_snap);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- final int widthPadding = getPaddingLeft() + getPaddingRight();
- int bubbleWidth = getMeasuredWidth() - widthPadding;
- final int maxWidth = MeasureSpec.getSize(widthMeasureSpec) - widthPadding;
- // Round up to next snapWidthPixels
- bubbleWidth = Math.min(maxWidth,
- (int) (Math.ceil(bubbleWidth / (float) mSnapWidthPixels) * mSnapWidthPixels));
- super.onMeasure(
- MeasureSpec.makeMeasureSpec(bubbleWidth + widthPadding, MeasureSpec.EXACTLY),
- heightMeasureSpec);
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/MessageDetailsDialog.java b/src/com/android/messaging/ui/conversation/MessageDetailsDialog.java
deleted file mode 100644
index 89b9148..0000000
--- a/src/com/android/messaging/ui/conversation/MessageDetailsDialog.java
+++ /dev/null
@@ -1,381 +0,0 @@
-/*
- * 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.AlertDialog;
-import android.content.Context;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.text.TextUtils;
-import android.text.format.Formatter;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.BugleDatabaseOperations;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.data.ConversationMessageData;
-import com.android.messaging.datamodel.data.ConversationParticipantsData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.mmslib.pdu.PduHeaders;
-import com.android.messaging.sms.DatabaseMessages.MmsMessage;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.Assert.DoesNotRunOnMainThread;
-import com.android.messaging.util.Dates;
-import com.android.messaging.util.DebugUtils;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.SafeAsyncTask;
-
-import java.util.List;
-
-public class MessageDetailsDialog {
- private static final String RECIPIENT_SEPARATOR = ", ";
-
- // All methods are static, no creating this class
- private MessageDetailsDialog() {
- }
-
- public static void show(final Context context, final ConversationMessageData data,
- final ConversationParticipantsData participants, final ParticipantData self) {
- if (DebugUtils.isDebugEnabled()) {
- new SafeAsyncTask<Void, Void, String>() {
- @Override
- protected String doInBackgroundTimed(Void... params) {
- return getMessageDetails(context, data, participants, self);
- }
-
- @Override
- protected void onPostExecute(String messageDetails) {
- showDialog(context, messageDetails);
- }
- }.executeOnThreadPool(null, null, null);
- } else {
- String messageDetails = getMessageDetails(context, data, participants, self);
- showDialog(context, messageDetails);
- }
- }
-
- private static String getMessageDetails(final Context context,
- final ConversationMessageData data,
- final ConversationParticipantsData participants, final ParticipantData self) {
- String messageDetails = null;
- if (data.getIsSms()) {
- messageDetails = getSmsMessageDetails(data, participants, self);
- } else {
- // TODO: Handle SMS_TYPE_MMS_PUSH_NOTIFICATION type differently?
- messageDetails = getMmsMessageDetails(context, data, participants, self);
- }
-
- return messageDetails;
- }
-
- private static void showDialog(final Context context, String messageDetails) {
- if (!TextUtils.isEmpty(messageDetails)) {
- new AlertDialog.Builder(context)
- .setTitle(R.string.message_details_title)
- .setMessage(messageDetails)
- .setCancelable(true)
- .show();
- }
- }
-
- /**
- * Return a string, separated by newlines, that contains a number of labels and values
- * for this sms message. The string will be displayed in a modal dialog.
- * @return string list of various message properties
- */
- private static String getSmsMessageDetails(final ConversationMessageData data,
- final ConversationParticipantsData participants, final ParticipantData self) {
- final Resources res = Factory.get().getApplicationContext().getResources();
- final StringBuilder details = new StringBuilder();
-
- // Type: Text message
- details.append(res.getString(R.string.message_type_label));
- details.append(res.getString(R.string.text_message));
-
- // From: +1425xxxxxxx
- // or To: +1425xxxxxxx
- final String rawSender = data.getSenderNormalizedDestination();
- if (!TextUtils.isEmpty(rawSender)) {
- details.append('\n');
- details.append(res.getString(R.string.from_label));
- details.append(rawSender);
- }
- final String rawRecipients = getRecipientParticipantString(participants,
- data.getParticipantId(), data.getIsIncoming(), data.getSelfParticipantId());
- if (!TextUtils.isEmpty(rawRecipients)) {
- details.append('\n');
- details.append(res.getString(R.string.to_address_label));
- details.append(rawRecipients);
- }
-
- // Sent: Mon 11:42AM
- if (data.getIsIncoming()) {
- if (data.getSentTimeStamp() != MmsUtils.INVALID_TIMESTAMP) {
- details.append('\n');
- details.append(res.getString(R.string.sent_label));
- details.append(
- Dates.getMessageDetailsTimeString(data.getSentTimeStamp()).toString());
- }
- }
-
- // Sent: Mon 11:43AM
- // or Received: Mon 11:43AM
- appendSentOrReceivedTimestamp(res, details, data);
-
- appendSimInfo(res, self, details);
-
- if (DebugUtils.isDebugEnabled()) {
- appendDebugInfo(details, data);
- }
-
- return details.toString();
- }
-
- /**
- * Return a string, separated by newlines, that contains a number of labels and values
- * for this mms message. The string will be displayed in a modal dialog.
- * @return string list of various message properties
- */
- private static String getMmsMessageDetails(Context context, final ConversationMessageData data,
- final ConversationParticipantsData participants, final ParticipantData self) {
- final Resources res = Factory.get().getApplicationContext().getResources();
- // TODO: when we support non-auto-download of mms messages, we'll have to handle
- // the case when the message is a PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND and display
- // something different. See the Messaging app's MessageUtils.getNotificationIndDetails()
-
- final StringBuilder details = new StringBuilder();
-
- // Type: Multimedia message.
- details.append(res.getString(R.string.message_type_label));
- details.append(res.getString(R.string.multimedia_message));
-
- // From: +1425xxxxxxx
- final String rawSender = data.getSenderNormalizedDestination();
- details.append('\n');
- details.append(res.getString(R.string.from_label));
- details.append(!TextUtils.isEmpty(rawSender) ? rawSender :
- res.getString(R.string.hidden_sender_address));
-
- // To: +1425xxxxxxx
- final String rawRecipients = getRecipientParticipantString(participants,
- data.getParticipantId(), data.getIsIncoming(), data.getSelfParticipantId());
- if (!TextUtils.isEmpty(rawRecipients)) {
- details.append('\n');
- details.append(res.getString(R.string.to_address_label));
- details.append(rawRecipients);
- }
-
- // Sent: Tue 3:05PM
- // or Received: Tue 3:05PM
- appendSentOrReceivedTimestamp(res, details, data);
-
- // Subject: You're awesome
- details.append('\n');
- details.append(res.getString(R.string.subject_label));
- if (!TextUtils.isEmpty(MmsUtils.cleanseMmsSubject(res, data.getMmsSubject()))) {
- details.append(data.getMmsSubject());
- }
-
- // Priority: High/Normal/Low
- details.append('\n');
- details.append(res.getString(R.string.priority_label));
- details.append(getPriorityDescription(res, data.getSmsPriority()));
-
- // Message size: 30 KB
- if (data.getSmsMessageSize() > 0) {
- details.append('\n');
- details.append(res.getString(R.string.message_size_label));
- details.append(Formatter.formatFileSize(context, data.getSmsMessageSize()));
- }
-
- appendSimInfo(res, self, details);
-
- if (DebugUtils.isDebugEnabled()) {
- appendDebugInfo(details, data);
- }
-
- return details.toString();
- }
-
- private static void appendSentOrReceivedTimestamp(Resources res, StringBuilder details,
- ConversationMessageData data) {
- int labelId = -1;
- if (data.getIsIncoming()) {
- labelId = R.string.received_label;
- } else if (data.getIsSendComplete()) {
- labelId = R.string.sent_label;
- }
- if (labelId >= 0) {
- details.append('\n');
- details.append(res.getString(labelId));
- details.append(
- Dates.getMessageDetailsTimeString(data.getReceivedTimeStamp()).toString());
- }
- }
-
- @DoesNotRunOnMainThread
- private static void appendDebugInfo(StringBuilder details, ConversationMessageData data) {
- // We grab the thread id from the database, so this needs to run in the background
- Assert.isNotMainThread();
- details.append("\n\n");
- details.append("DEBUG");
-
- details.append('\n');
- details.append("Message id: ");
- details.append(data.getMessageId());
-
- final String telephonyUri = data.getSmsMessageUri();
- details.append('\n');
- details.append("Telephony uri: ");
- details.append(telephonyUri);
-
- final String conversationId = data.getConversationId();
-
- if (conversationId == null) {
- return;
- }
-
- details.append('\n');
- details.append("Conversation id: ");
- details.append(conversationId);
-
- final long threadId = BugleDatabaseOperations.getThreadId(DataModel.get().getDatabase(),
- conversationId);
-
- details.append('\n');
- details.append("Conversation telephony thread id: ");
- details.append(threadId);
-
- MmsMessage mms = null;
-
- if (data.getIsMms()) {
- if (telephonyUri == null) {
- return;
- }
- mms = MmsUtils.loadMms(Uri.parse(telephonyUri));
- if (mms == null) {
- return;
- }
-
- // We log the thread id again to check that they are internally consistent
- final long mmsThreadId = mms.mThreadId;
- details.append('\n');
- details.append("Telephony thread id: ");
- details.append(mmsThreadId);
-
- // Log the MMS content location
- final String mmsContentLocation = mms.mContentLocation;
- details.append('\n');
- details.append("Content location URL: ");
- details.append(mmsContentLocation);
- }
-
- final String recipientsString = MmsUtils.getRawRecipientIdsForThread(threadId);
- if (recipientsString != null) {
- details.append('\n');
- details.append("Thread recipient ids: ");
- details.append(recipientsString);
- }
-
- final List<String> recipients = MmsUtils.getRecipientsByThread(threadId);
- if (recipients != null) {
- details.append('\n');
- details.append("Thread recipients: ");
- details.append(recipients.toString());
-
- if (mms != null) {
- final String from = MmsUtils.getMmsSender(recipients, mms.getUri());
- details.append('\n');
- details.append("Sender: ");
- details.append(from);
- }
- }
- }
-
- private static String getRecipientParticipantString(
- final ConversationParticipantsData participants, final String senderId,
- final boolean addSelf, final String selfId) {
- final StringBuilder recipients = new StringBuilder();
- for (final ParticipantData participant : participants) {
- if (TextUtils.equals(participant.getId(), senderId)) {
- // Don't add sender
- continue;
- }
- if (participant.isSelf() &&
- (!participant.getId().equals(selfId) || !addSelf)) {
- // For self participants, don't add the one that's not relevant to this message
- // or if we are asked not to add self
- continue;
- }
- final String phoneNumber = participant.getNormalizedDestination();
- // Don't add empty number. This should not happen. But if that happens
- // we should not add it.
- if (!TextUtils.isEmpty(phoneNumber)) {
- if (recipients.length() > 0) {
- recipients.append(RECIPIENT_SEPARATOR);
- }
- recipients.append(phoneNumber);
- }
- }
- return recipients.toString();
- }
-
- /**
- * Convert the numeric mms priority into a human-readable string
- * @param res
- * @param priorityValue coded PduHeader priority
- * @return string representation of the priority
- */
- private static String getPriorityDescription(final Resources res, final int priorityValue) {
- switch(priorityValue) {
- case PduHeaders.PRIORITY_HIGH:
- return res.getString(R.string.priority_high);
- case PduHeaders.PRIORITY_LOW:
- return res.getString(R.string.priority_low);
- case PduHeaders.PRIORITY_NORMAL:
- default:
- return res.getString(R.string.priority_normal);
- }
- }
-
- private static void appendSimInfo(final Resources res,
- final ParticipantData self, final StringBuilder outString) {
- if (!OsUtil.isAtLeastL_MR1()
- || self == null
- || PhoneUtils.getDefault().getActiveSubscriptionCount() < 2) {
- return;
- }
- // The appended SIM info would look like:
- // SIM: SUB 01
- // or SIM: SIM 1
- // or SIM: Unknown
- Assert.isTrue(self.isSelf());
- outString.append('\n');
- outString.append(res.getString(R.string.sim_label));
- if (self.isActiveSubscription() && !self.isDefaultSelf()) {
- final String subscriptionName = self.getSubscriptionName();
- if (TextUtils.isEmpty(subscriptionName)) {
- outString.append(res.getString(R.string.sim_slot_identifier,
- self.getDisplaySlotId()));
- } else {
- outString.append(subscriptionName);
- }
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/SimIconView.java b/src/com/android/messaging/ui/conversation/SimIconView.java
deleted file mode 100644
index e2e446c..0000000
--- a/src/com/android/messaging/ui/conversation/SimIconView.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.content.Context;
-import android.graphics.Outline;
-import android.net.Uri;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-
-import com.android.messaging.ui.ContactIconView;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.AvatarUriUtil;
-import com.android.messaging.util.OsUtil;
-
-/**
- * Shows SIM avatar icon in the SIM switcher / Self-send button.
- */
-public class SimIconView extends ContactIconView {
- public SimIconView(Context context, AttributeSet attrs) {
- super(context, attrs);
- if (OsUtil.isAtLeastL()) {
- setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View v, Outline outline) {
- outline.setOval(0, 0, v.getWidth(), v.getHeight());
- }
- });
- }
- }
-
- @Override
- protected void maybeInitializeOnClickListener() {
- // TODO: SIM icon view shouldn't consume or handle clicks, but it should if
- // this is the send button for the only SIM in the device or if MSIM is not supported.
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/SimSelectorItemView.java b/src/com/android/messaging/ui/conversation/SimSelectorItemView.java
deleted file mode 100644
index 3058d31..0000000
--- a/src/com/android/messaging/ui/conversation/SimSelectorItemView.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.content.Context;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
-import com.android.messaging.util.Assert;
-
-/**
- * Shows a view for a SIM in the SIM selector.
- */
-public class SimSelectorItemView extends LinearLayout {
- public interface HostInterface {
- void onSimItemClicked(SubscriptionListEntry item);
- }
-
- private SubscriptionListEntry mData;
- private TextView mNameTextView;
- private TextView mDetailsTextView;
- private SimIconView mSimIconView;
- private HostInterface mHost;
-
- public SimSelectorItemView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- mNameTextView = (TextView) findViewById(R.id.name);
- mDetailsTextView = (TextView) findViewById(R.id.details);
- mSimIconView = (SimIconView) findViewById(R.id.sim_icon);
- setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mHost.onSimItemClicked(mData);
- }
- });
- }
-
- public void bind(final SubscriptionListEntry simEntry) {
- Assert.notNull(simEntry);
- mData = simEntry;
- updateViewAppearance();
- }
-
- public void setHostInterface(final HostInterface host) {
- mHost = host;
- }
-
- private void updateViewAppearance() {
- Assert.notNull(mData);
- final String displayName = mData.displayName;
- if (TextUtils.isEmpty(displayName)) {
- mNameTextView.setVisibility(GONE);
- } else {
- mNameTextView.setVisibility(VISIBLE);
- mNameTextView.setText(displayName);
- }
-
- final String details = mData.displayDestination;
- if (TextUtils.isEmpty(details)) {
- mDetailsTextView.setVisibility(GONE);
- } else {
- mDetailsTextView.setVisibility(VISIBLE);
- mDetailsTextView.setText(details);
- }
-
- mSimIconView.setImageResourceUri(mData.iconUri);
- }
-}
diff --git a/src/com/android/messaging/ui/conversation/SimSelectorView.java b/src/com/android/messaging/ui/conversation/SimSelectorView.java
deleted file mode 100644
index b07ff19..0000000
--- a/src/com/android/messaging/ui/conversation/SimSelectorView.java
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * 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.content.Context;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.Animation;
-import android.view.animation.TranslateAnimation;
-import android.widget.ArrayAdapter;
-import android.widget.FrameLayout;
-import android.widget.ListView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.SubscriptionListData;
-import com.android.messaging.datamodel.data.SubscriptionListData.SubscriptionListEntry;
-import com.android.messaging.util.UiUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Displays a SIM selector above the compose message view and overlays the message list.
- */
-public class SimSelectorView extends FrameLayout implements SimSelectorItemView.HostInterface {
- public interface SimSelectorViewListener {
- void onSimItemClicked(SubscriptionListEntry item);
- void onSimSelectorVisibilityChanged(boolean visible);
- }
-
- private ListView mSimListView;
- private final SimSelectorAdapter mAdapter;
- private boolean mShow;
- private SimSelectorViewListener mListener;
- private int mItemLayoutId;
-
- public SimSelectorView(Context context, AttributeSet attrs) {
- super(context, attrs);
- mAdapter = new SimSelectorAdapter(getContext());
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mSimListView = (ListView) findViewById(R.id.sim_list);
- mSimListView.setAdapter(mAdapter);
-
- // Clicking anywhere outside the switcher list should dismiss.
- setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- showOrHide(false, true);
- }
- });
- }
-
- public void bind(final SubscriptionListData data) {
- mAdapter.bindData(data.getActiveSubscriptionEntriesExcludingDefault());
- }
-
- public void setItemLayoutId(final int layoutId) {
- mItemLayoutId = layoutId;
- }
-
- public void setListener(final SimSelectorViewListener listener) {
- mListener = listener;
- }
-
- public void toggleVisibility() {
- showOrHide(!mShow, true);
- }
-
- public void showOrHide(final boolean show, final boolean animate) {
- final boolean oldShow = mShow;
- mShow = show && mAdapter.getCount() > 1;
- if (oldShow != mShow) {
- if (mListener != null) {
- mListener.onSimSelectorVisibilityChanged(mShow);
- }
-
- if (animate) {
- // Fade in the background pane.
- setVisibility(VISIBLE);
- setAlpha(mShow ? 0.0f : 1.0f);
- animate().alpha(mShow ? 1.0f : 0.0f)
- .setDuration(UiUtils.REVEAL_ANIMATION_DURATION)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- setAlpha(1.0f);
- setVisibility(mShow ? VISIBLE : GONE);
- }
- });
- } else {
- setVisibility(mShow ? VISIBLE : GONE);
- }
-
- // Slide in the SIM selector list via a translate animation.
- mSimListView.setVisibility(mShow ? VISIBLE : GONE);
- if (animate) {
- mSimListView.clearAnimation();
- final TranslateAnimation translateAnimation = new TranslateAnimation(
- Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0,
- Animation.RELATIVE_TO_SELF, mShow ? 1.0f : 0.0f,
- Animation.RELATIVE_TO_SELF, mShow ? 0.0f : 1.0f);
- translateAnimation.setInterpolator(UiUtils.EASE_OUT_INTERPOLATOR);
- translateAnimation.setDuration(UiUtils.REVEAL_ANIMATION_DURATION);
- mSimListView.startAnimation(translateAnimation);
- }
- }
- }
-
- /**
- * An adapter that takes a list of SubscriptionListEntry and displays them as a list of
- * available SIMs in the SIM selector.
- */
- private class SimSelectorAdapter extends ArrayAdapter<SubscriptionListEntry> {
- public SimSelectorAdapter(final Context context) {
- super(context, R.layout.sim_selector_item_view, new ArrayList<SubscriptionListEntry>());
- }
-
- public void bindData(final List<SubscriptionListEntry> newList) {
- clear();
- addAll(newList);
- notifyDataSetChanged();
- }
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parent) {
- SimSelectorItemView itemView;
- if (convertView != null && convertView instanceof SimSelectorItemView) {
- itemView = (SimSelectorItemView) convertView;
- } else {
- final LayoutInflater inflater = (LayoutInflater) getContext()
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- itemView = (SimSelectorItemView) inflater.inflate(mItemLayoutId,
- parent, false);
- itemView.setHostInterface(SimSelectorView.this);
- }
- itemView.bind(getItem(position));
- return itemView;
- }
- }
-
- @Override
- public void onSimItemClicked(SubscriptionListEntry item) {
- mListener.onSimItemClicked(item);
- showOrHide(false, true);
- }
-
- public boolean isOpen() {
- return mShow;
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java b/src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java
deleted file mode 100644
index dbbbb15..0000000
--- a/src/com/android/messaging/ui/conversationlist/AbstractConversationListActivity.java
+++ /dev/null
@@ -1,339 +0,0 @@
-/*
- * 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.conversationlist;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Fragment;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.view.View;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.action.DeleteConversationAction;
-import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction;
-import com.android.messaging.datamodel.action.UpdateConversationOptionsAction;
-import com.android.messaging.datamodel.action.UpdateDestinationBlockedAction;
-import com.android.messaging.datamodel.data.ConversationListData;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.SnackBar;
-import com.android.messaging.ui.SnackBarInteraction;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.contact.AddContactsConfirmationDialog;
-import com.android.messaging.ui.conversationlist.ConversationListFragment.ConversationListFragmentHost;
-import com.android.messaging.ui.conversationlist.MultiSelectActionModeCallback.SelectedConversation;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.DebugUtils;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.Trace;
-import com.android.messaging.util.UiUtils;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import javax.annotation.Nullable;
-
-/**
- * Base class for many Conversation List activities. This will handle the common actions of multi
- * select and common launching of intents.
- */
-public abstract class AbstractConversationListActivity extends BugleActionBarActivity
- implements ConversationListFragmentHost, MultiSelectActionModeCallback.Listener {
-
- private static final int REQUEST_SET_DEFAULT_SMS_APP = 1;
-
- protected ConversationListFragment mConversationListFragment;
-
- @Override
- public void onAttachFragment(final Fragment fragment) {
- Trace.beginSection("AbstractConversationListActivity.onAttachFragment");
- // Fragment could be debug dialog
- if (fragment instanceof ConversationListFragment) {
- mConversationListFragment = (ConversationListFragment) fragment;
- mConversationListFragment.setHost(this);
- }
- Trace.endSection();
- }
-
- @Override
- public void onBackPressed() {
- // If action mode is active dismiss it
- if (getActionMode() != null) {
- dismissActionMode();
- return;
- }
- super.onBackPressed();
- }
-
- protected void startMultiSelectActionMode() {
- startActionMode(new MultiSelectActionModeCallback(this));
- }
-
- protected void exitMultiSelectState() {
- mConversationListFragment.showFab();
- dismissActionMode();
- mConversationListFragment.updateUi();
- }
-
- protected boolean isInConversationListSelectMode() {
- return getActionModeCallback() instanceof MultiSelectActionModeCallback;
- }
-
- @Override
- public boolean isSelectionMode() {
- return isInConversationListSelectMode();
- }
-
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
- }
-
- @Override
- public void onActionBarDelete(final Collection<SelectedConversation> conversations) {
- if (!PhoneUtils.getDefault().isDefaultSmsApp()) {
- // TODO: figure out a good way to combine this with the implementation in
- // ConversationFragment doing similar things
- final Activity activity = this;
- UiUtils.showSnackBarWithCustomAction(this,
- getWindow().getDecorView().getRootView(),
- getString(R.string.requires_default_sms_app),
- SnackBar.Action.createCustomAction(new Runnable() {
- @Override
- public void run() {
- final Intent intent =
- UIIntents.get().getChangeDefaultSmsAppIntent(activity);
- startActivityForResult(intent, REQUEST_SET_DEFAULT_SMS_APP);
- }
- },
- getString(R.string.requires_default_sms_change_button)),
- null /* interactions */,
- null /* placement */);
- return;
- }
-
- new AlertDialog.Builder(this)
- .setTitle(getResources().getQuantityString(
- R.plurals.delete_conversations_confirmation_dialog_title,
- conversations.size()))
- .setPositiveButton(R.string.delete_conversation_confirmation_button,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface dialog,
- final int button) {
- for (final SelectedConversation conversation : conversations) {
- DeleteConversationAction.deleteConversation(
- conversation.conversationId,
- conversation.timestamp);
- }
- exitMultiSelectState();
- }
- })
- .setNegativeButton(R.string.delete_conversation_decline_button, null)
- .show();
- }
-
- @Override
- public void onActionBarArchive(final Iterable<SelectedConversation> conversations,
- final boolean isToArchive) {
- final ArrayList<String> conversationIds = new ArrayList<String>();
- for (final SelectedConversation conversation : conversations) {
- final String conversationId = conversation.conversationId;
- conversationIds.add(conversationId);
- if (isToArchive) {
- UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
- } else {
- UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
- }
- }
-
- final Runnable undoRunnable = new Runnable() {
- @Override
- public void run() {
- for (final String conversationId : conversationIds) {
- if (isToArchive) {
- UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
- } else {
- UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
- }
- }
- }
- };
-
- final int textId =
- isToArchive ? R.string.archived_toast_message : R.string.unarchived_toast_message;
- final String message = getResources().getString(textId, conversationIds.size());
- UiUtils.showSnackBar(this, findViewById(android.R.id.list), message, undoRunnable,
- SnackBar.Action.SNACK_BAR_UNDO,
- mConversationListFragment.getSnackBarInteractions());
- exitMultiSelectState();
- }
-
- @Override
- public void onActionBarNotification(final Iterable<SelectedConversation> conversations,
- final boolean isNotificationOn) {
- for (final SelectedConversation conversation : conversations) {
- UpdateConversationOptionsAction.enableConversationNotifications(
- conversation.conversationId, isNotificationOn);
- }
-
- final int textId = isNotificationOn ?
- R.string.notification_on_toast_message : R.string.notification_off_toast_message;
- final String message = getResources().getString(textId, 1);
- UiUtils.showSnackBar(this, findViewById(android.R.id.list), message,
- null /* undoRunnable */,
- SnackBar.Action.SNACK_BAR_UNDO, mConversationListFragment.getSnackBarInteractions());
- exitMultiSelectState();
- }
-
- @Override
- public void onActionBarAddContact(final SelectedConversation conversation) {
- final Uri avatarUri;
- if (conversation.icon != null) {
- avatarUri = Uri.parse(conversation.icon);
- } else {
- avatarUri = null;
- }
- final AddContactsConfirmationDialog dialog = new AddContactsConfirmationDialog(
- this, avatarUri, conversation.otherParticipantNormalizedDestination);
- dialog.show();
- exitMultiSelectState();
- }
-
- @Override
- public void onActionBarBlock(final SelectedConversation conversation) {
- final Resources res = getResources();
- new AlertDialog.Builder(this)
- .setTitle(res.getString(R.string.block_confirmation_title,
- conversation.otherParticipantNormalizedDestination))
- .setMessage(res.getString(R.string.block_confirmation_message))
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(final DialogInterface arg0, final int arg1) {
- final Context context = AbstractConversationListActivity.this;
- final View listView = findViewById(android.R.id.list);
- final List<SnackBarInteraction> interactions =
- mConversationListFragment.getSnackBarInteractions();
- final UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener
- undoListener =
- new UpdateDestinationBlockedActionSnackBar(
- context, listView, null /* undoRunnable */,
- interactions);
- final Runnable undoRunnable = new Runnable() {
- @Override
- public void run() {
- UpdateDestinationBlockedAction.updateDestinationBlocked(
- conversation.otherParticipantNormalizedDestination, false,
- conversation.conversationId,
- undoListener);
- }
- };
- final UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener
- listener = new UpdateDestinationBlockedActionSnackBar(
- context, listView, undoRunnable, interactions);
- UpdateDestinationBlockedAction.updateDestinationBlocked(
- conversation.otherParticipantNormalizedDestination, true,
- conversation.conversationId,
- listener);
- exitMultiSelectState();
- }
- })
- .create()
- .show();
- }
-
- @Override
- public void onConversationClick(final ConversationListData listData,
- final ConversationListItemData conversationListItemData,
- final boolean isLongClick,
- final ConversationListItemView conversationView) {
- if (isLongClick && !isInConversationListSelectMode()) {
- startMultiSelectActionMode();
- }
-
- if (isInConversationListSelectMode()) {
- final MultiSelectActionModeCallback multiSelectActionMode =
- (MultiSelectActionModeCallback) getActionModeCallback();
- multiSelectActionMode.toggleSelect(listData, conversationListItemData);
- mConversationListFragment.updateUi();
- } else {
- final String conversationId = conversationListItemData.getConversationId();
- Bundle sceneTransitionAnimationOptions = null;
- boolean hasCustomTransitions = false;
-
- UIIntents.get().launchConversationActivity(
- this, conversationId, null,
- sceneTransitionAnimationOptions,
- hasCustomTransitions);
- }
- }
-
- @Override
- public void onCreateConversationClick() {
- UIIntents.get().launchCreateNewConversationActivity(this, null);
- }
-
-
- @Override
- public boolean isConversationSelected(final String conversationId) {
- return isInConversationListSelectMode() &&
- ((MultiSelectActionModeCallback) getActionModeCallback()).isSelected(
- conversationId);
- }
-
- public void onActionBarDebug() {
- DebugUtils.showDebugOptions(this);
- }
-
- private static class UpdateDestinationBlockedActionSnackBar
- implements UpdateDestinationBlockedAction.UpdateDestinationBlockedActionListener {
- private final Context mContext;
- private final View mParentView;
- private final Runnable mUndoRunnable;
- private final List<SnackBarInteraction> mInteractions;
-
- UpdateDestinationBlockedActionSnackBar(final Context context,
- @NonNull final View parentView, @Nullable final Runnable undoRunnable,
- @Nullable List<SnackBarInteraction> interactions) {
- mContext = context;
- mParentView = parentView;
- mUndoRunnable = undoRunnable;
- mInteractions = interactions;
- }
-
- @Override
- public void onUpdateDestinationBlockedAction(
- final UpdateDestinationBlockedAction action,
- final boolean success, final boolean block,
- final String destination) {
- if (success) {
- final int messageId = block ? R.string.blocked_toast_message
- : R.string.unblocked_toast_message;
- final String message = mContext.getResources().getString(messageId, 1);
- UiUtils.showSnackBar(mContext, mParentView, message, mUndoRunnable,
- SnackBar.Action.SNACK_BAR_UNDO, mInteractions);
- }
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/ArchivedConversationListActivity.java b/src/com/android/messaging/ui/conversationlist/ArchivedConversationListActivity.java
deleted file mode 100644
index 366c7d3..0000000
--- a/src/com/android/messaging/ui/conversationlist/ArchivedConversationListActivity.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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.conversationlist;
-
-import android.graphics.drawable.ColorDrawable;
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.view.Menu;
-import android.view.MenuItem;
-
-import com.android.messaging.R;
-import com.android.messaging.util.DebugUtils;
-
-public class ArchivedConversationListActivity extends AbstractConversationListActivity {
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final ConversationListFragment fragment =
- ConversationListFragment.createArchivedConversationListFragment();
- getFragmentManager().beginTransaction().add(android.R.id.content, fragment).commit();
- invalidateActionBar();
- }
-
- protected void updateActionBar(ActionBar actionBar) {
- actionBar.setTitle(getString(R.string.archived_activity_title));
- actionBar.setDisplayShowTitleEnabled(true);
- actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.setBackgroundDrawable(new ColorDrawable(
- getResources().getColor(
- R.color.archived_conversation_action_bar_background_color_dark)));
- actionBar.show();
- super.updateActionBar(actionBar);
- }
-
- @Override
- public void onBackPressed() {
- if (isInConversationListSelectMode()) {
- exitMultiSelectState();
- } else {
- super.onBackPressed();
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- if (super.onCreateOptionsMenu(menu)) {
- return true;
- }
- getMenuInflater().inflate(R.menu.archived_conversation_list_menu, menu);
- final MenuItem item = menu.findItem(R.id.action_debug_options);
- if (item != null) {
- final boolean enableDebugItems = DebugUtils.isDebugEnabled();
- item.setVisible(enableDebugItems).setEnabled(enableDebugItems);
- }
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem menuItem) {
- switch(menuItem.getItemId()) {
- case R.id.action_debug_options:
- onActionBarDebug();
- return true;
- case android.R.id.home:
- onActionBarHome();
- return true;
- default:
- return super.onOptionsItemSelected(menuItem);
- }
- }
-
- @Override
- public void onActionBarHome() {
- onBackPressed();
- }
-
- @Override
- public boolean isSwipeAnimatable() {
- return false;
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListActivity.java b/src/com/android/messaging/ui/conversationlist/ConversationListActivity.java
deleted file mode 100644
index f8abe81..0000000
--- a/src/com/android/messaging/ui/conversationlist/ConversationListActivity.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * 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.conversationlist;
-
-import android.graphics.drawable.ColorDrawable;
-import android.os.Bundle;
-import android.support.v7.app.ActionBar;
-import android.view.Menu;
-import android.view.MenuItem;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.DebugUtils;
-import com.android.messaging.util.Trace;
-
-public class ConversationListActivity extends AbstractConversationListActivity {
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- Trace.beginSection("ConversationListActivity.onCreate");
- super.onCreate(savedInstanceState);
- setContentView(R.layout.conversation_list_activity);
- Trace.endSection();
- invalidateActionBar();
- }
-
- @Override
- protected void updateActionBar(final ActionBar actionBar) {
- actionBar.setTitle(getString(R.string.app_name));
- actionBar.setDisplayShowTitleEnabled(true);
- actionBar.setDisplayHomeAsUpEnabled(false);
- actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
- actionBar.setBackgroundDrawable(new ColorDrawable(
- getResources().getColor(R.color.action_bar_background_color)));
- actionBar.show();
- super.updateActionBar(actionBar);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- // Invalidate the menu as items that are based on settings may have changed
- // while not in the app (e.g. Talkback enabled/disable affects new conversation
- // button)
- supportInvalidateOptionsMenu();
- }
-
- @Override
- public void onBackPressed() {
- if (isInConversationListSelectMode()) {
- exitMultiSelectState();
- } else {
- super.onBackPressed();
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(final Menu menu) {
- if (super.onCreateOptionsMenu(menu)) {
- return true;
- }
- getMenuInflater().inflate(R.menu.conversation_list_fragment_menu, menu);
- final MenuItem item = menu.findItem(R.id.action_debug_options);
- if (item != null) {
- final boolean enableDebugItems = DebugUtils.isDebugEnabled();
- item.setVisible(enableDebugItems).setEnabled(enableDebugItems);
- }
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem menuItem) {
- switch(menuItem.getItemId()) {
- case R.id.action_start_new_conversation:
- onActionBarStartNewConversation();
- return true;
- case R.id.action_settings:
- onActionBarSettings();
- return true;
- case R.id.action_debug_options:
- onActionBarDebug();
- return true;
- case R.id.action_show_archived:
- onActionBarArchived();
- return true;
- case R.id.action_show_blocked_contacts:
- onActionBarBlockedParticipants();
- return true;
- }
- return super.onOptionsItemSelected(menuItem);
- }
-
- @Override
- public void onActionBarHome() {
- exitMultiSelectState();
- }
-
- public void onActionBarStartNewConversation() {
- UIIntents.get().launchCreateNewConversationActivity(this, null);
- }
-
- public void onActionBarSettings() {
- UIIntents.get().launchSettingsActivity(this);
- }
-
- public void onActionBarBlockedParticipants() {
- UIIntents.get().launchBlockedParticipantsActivity(this);
- }
-
- public void onActionBarArchived() {
- UIIntents.get().launchArchivedConversationsActivity(this);
- }
-
- @Override
- public boolean isSwipeAnimatable() {
- return !isInConversationListSelectMode();
- }
-
- @Override
- public void onWindowFocusChanged(final boolean hasFocus) {
- super.onWindowFocusChanged(hasFocus);
- final ConversationListFragment conversationListFragment =
- (ConversationListFragment) getFragmentManager().findFragmentById(
- R.id.conversation_list_fragment);
- // 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 && conversationListFragment != null) {
- conversationListFragment.setScrolledToNewestConversationIfNeeded();
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListAdapter.java b/src/com/android/messaging/ui/conversationlist/ConversationListAdapter.java
deleted file mode 100644
index 629c4ae..0000000
--- a/src/com/android/messaging/ui/conversationlist/ConversationListAdapter.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.conversationlist;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.CursorRecyclerAdapter;
-import com.android.messaging.ui.conversationlist.ConversationListItemView.HostInterface;
-
-/**
- * Provides an interface to expose Conversation List Cursor data to a UI widget like a ListView.
- */
-public class ConversationListAdapter
- extends CursorRecyclerAdapter<ConversationListAdapter.ConversationListViewHolder> {
-
- private final ConversationListItemView.HostInterface mClivHostInterface;
-
- public ConversationListAdapter(final Context context, final Cursor cursor,
- final ConversationListItemView.HostInterface clivHostInterface) {
- super(context, cursor, 0);
- mClivHostInterface = clivHostInterface;
- setHasStableIds(true);
- }
-
- /**
- * @see com.android.messaging.ui.CursorRecyclerAdapter#bindViewHolder(
- * android.support.v7.widget.RecyclerView.ViewHolder, android.content.Context,
- * android.database.Cursor)
- */
- @Override
- public void bindViewHolder(final ConversationListViewHolder holder, final Context context,
- final Cursor cursor) {
- final ConversationListItemView conversationListItemView = holder.mView;
- conversationListItemView.bind(cursor, mClivHostInterface);
- }
-
- @Override
- public ConversationListViewHolder createViewHolder(final Context context,
- final ViewGroup parent, final int viewType) {
- final LayoutInflater layoutInflater = LayoutInflater.from(context);
- final ConversationListItemView itemView =
- (ConversationListItemView) layoutInflater.inflate(
- R.layout.conversation_list_item_view, null);
- return new ConversationListViewHolder(itemView);
- }
-
- /**
- * ViewHolder that holds a ConversationListItemView.
- */
- public static class ConversationListViewHolder extends RecyclerView.ViewHolder {
- final ConversationListItemView mView;
-
- public ConversationListViewHolder(final ConversationListItemView itemView) {
- super(itemView);
- mView = itemView;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListFragment.java b/src/com/android/messaging/ui/conversationlist/ConversationListFragment.java
deleted file mode 100644
index 2f868d4..0000000
--- a/src/com/android/messaging/ui/conversationlist/ConversationListFragment.java
+++ /dev/null
@@ -1,446 +0,0 @@
-/*
- * 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.conversationlist;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.support.v4.view.ViewCompat;
-import android.support.v4.view.ViewGroupCompat;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.view.ViewGroup.MarginLayoutParams;
-import android.view.ViewPropertyAnimator;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.AbsListView;
-import android.widget.ImageView;
-
-import com.android.messaging.R;
-import com.android.messaging.annotation.VisibleForAnimation;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.ConversationListData;
-import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.ui.BugleAnimationTags;
-import com.android.messaging.ui.ListEmptyView;
-import com.android.messaging.ui.SnackBarInteraction;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ImeUtil;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.UiUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Shows a list of conversations.
- */
-public class ConversationListFragment extends Fragment implements ConversationListDataListener,
- ConversationListItemView.HostInterface {
- private static final String BUNDLE_ARCHIVED_MODE = "archived_mode";
- private static final String BUNDLE_FORWARD_MESSAGE_MODE = "forward_message_mode";
- private static final boolean VERBOSE = false;
-
- private MenuItem mShowBlockedMenuItem;
- private boolean mArchiveMode;
- private boolean mBlockedAvailable;
- private boolean mForwardMessageMode;
-
- public interface ConversationListFragmentHost {
- public void onConversationClick(final ConversationListData listData,
- final ConversationListItemData conversationListItemData,
- final boolean isLongClick,
- final ConversationListItemView conversationView);
- public void onCreateConversationClick();
- public boolean isConversationSelected(final String conversationId);
- public boolean isSwipeAnimatable();
- public boolean isSelectionMode();
- public boolean hasWindowFocus();
- }
-
- private ConversationListFragmentHost mHost;
- private RecyclerView mRecyclerView;
- private ImageView mStartNewConversationButton;
- private ListEmptyView mEmptyListMessageView;
- private ConversationListAdapter mAdapter;
-
- // Saved Instance State Data - only for temporal data which is nice to maintain but not
- // critical for correctness.
- private static final String SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY =
- "conversationListViewState";
- private Parcelable mListState;
-
- @VisibleForTesting
- final Binding<ConversationListData> mListBinding = BindingBase.createBinding(this);
-
- public static ConversationListFragment createArchivedConversationListFragment() {
- return createConversationListFragment(BUNDLE_ARCHIVED_MODE);
- }
-
- public static ConversationListFragment createForwardMessageConversationListFragment() {
- return createConversationListFragment(BUNDLE_FORWARD_MESSAGE_MODE);
- }
-
- public static ConversationListFragment createConversationListFragment(String modeKeyName) {
- final ConversationListFragment fragment = new ConversationListFragment();
- final Bundle bundle = new Bundle();
- bundle.putBoolean(modeKeyName, true);
- fragment.setArguments(bundle);
- return fragment;
- }
-
- /**
- * {@inheritDoc} from Fragment
- */
- @Override
- public void onCreate(final Bundle bundle) {
- super.onCreate(bundle);
- mListBinding.getData().init(getLoaderManager(), mListBinding);
- mAdapter = new ConversationListAdapter(getActivity(), null, this);
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- Assert.notNull(mHost);
- setScrolledToNewestConversationIfNeeded();
-
- updateUi();
- }
-
- public void setScrolledToNewestConversationIfNeeded() {
- if (!mArchiveMode
- && !mForwardMessageMode
- && isScrolledToFirstConversation()
- && mHost.hasWindowFocus()) {
- mListBinding.getData().setScrolledToNewestConversation(true);
- }
- }
-
- private boolean isScrolledToFirstConversation() {
- int firstItemPosition = ((LinearLayoutManager) mRecyclerView.getLayoutManager())
- .findFirstCompletelyVisibleItemPosition();
- return firstItemPosition == 0;
- }
-
- /**
- * {@inheritDoc} from Fragment
- */
- @Override
- public void onDestroy() {
- super.onDestroy();
- mListBinding.unbind();
- mHost = null;
- }
-
- /**
- * {@inheritDoc} from Fragment
- */
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedInstanceState) {
- final ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.conversation_list_fragment,
- container, false);
- mRecyclerView = (RecyclerView) rootView.findViewById(android.R.id.list);
- mEmptyListMessageView = (ListEmptyView) rootView.findViewById(R.id.no_conversations_view);
- mEmptyListMessageView.setImageHint(R.drawable.ic_oobe_conv_list);
- // The default behavior for default layout param generation by LinearLayoutManager is to
- // provide width and height of WRAP_CONTENT, but this is not desirable for
- // ConversationListFragment; the view in each row should be a width of MATCH_PARENT so that
- // the entire row is tappable.
- final Activity activity = getActivity();
- final LinearLayoutManager manager = new LinearLayoutManager(activity) {
- @Override
- public RecyclerView.LayoutParams generateDefaultLayoutParams() {
- return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- }
- };
- mRecyclerView.setLayoutManager(manager);
- mRecyclerView.setHasFixedSize(true);
- mRecyclerView.setAdapter(mAdapter);
- mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
- int mCurrentState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE;
-
- @Override
- public void onScrolled(final RecyclerView recyclerView, final int dx, final int dy) {
- if (mCurrentState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL
- || mCurrentState == AbsListView.OnScrollListener.SCROLL_STATE_FLING) {
- ImeUtil.get().hideImeKeyboard(getActivity(), mRecyclerView);
- }
-
- if (isScrolledToFirstConversation()) {
- setScrolledToNewestConversationIfNeeded();
- } else {
- mListBinding.getData().setScrolledToNewestConversation(false);
- }
- }
-
- @Override
- public void onScrollStateChanged(final RecyclerView recyclerView, final int newState) {
- mCurrentState = newState;
- }
- });
- mRecyclerView.addOnItemTouchListener(new ConversationListSwipeHelper(mRecyclerView));
-
- if (savedInstanceState != null) {
- mListState = savedInstanceState.getParcelable(SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY);
- }
-
- mStartNewConversationButton = (ImageView) rootView.findViewById(
- R.id.start_new_conversation_button);
- if (mArchiveMode) {
- mStartNewConversationButton.setVisibility(View.GONE);
- } else {
- mStartNewConversationButton.setVisibility(View.VISIBLE);
- mStartNewConversationButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View clickView) {
- mHost.onCreateConversationClick();
- }
- });
- }
- ViewCompat.setTransitionName(mStartNewConversationButton, BugleAnimationTags.TAG_FABICON);
-
- // The root view has a non-null background, which by default is deemed by the framework
- // to be a "transition group," where all child views are animated together during an
- // activity transition. However, we want each individual items in the recycler view to
- // show explode animation themselves, so we explicitly tag the root view to be a non-group.
- ViewGroupCompat.setTransitionGroup(rootView, false);
-
- setHasOptionsMenu(true);
- return rootView;
- }
-
- @Override
- public void onAttach(final Activity activity) {
- super.onAttach(activity);
- if (VERBOSE) {
- LogUtil.v(LogUtil.BUGLE_TAG, "Attaching List");
- }
- final Bundle arguments = getArguments();
- if (arguments != null) {
- mArchiveMode = arguments.getBoolean(BUNDLE_ARCHIVED_MODE, false);
- mForwardMessageMode = arguments.getBoolean(BUNDLE_FORWARD_MESSAGE_MODE, false);
- }
- mListBinding.bind(DataModel.get().createConversationListData(activity, this, mArchiveMode));
- }
-
-
- @Override
- public void onSaveInstanceState(final Bundle outState) {
- super.onSaveInstanceState(outState);
- if (mListState != null) {
- outState.putParcelable(SAVED_INSTANCE_STATE_LIST_VIEW_STATE_KEY, mListState);
- }
- }
-
- @Override
- public void onPause() {
- super.onPause();
- mListState = mRecyclerView.getLayoutManager().onSaveInstanceState();
- mListBinding.getData().setScrolledToNewestConversation(false);
- }
-
- /**
- * Call this immediately after attaching the fragment
- */
- public void setHost(final ConversationListFragmentHost host) {
- Assert.isNull(mHost);
- mHost = host;
- }
-
- @Override
- public void onConversationListCursorUpdated(final ConversationListData data,
- final Cursor cursor) {
- mListBinding.ensureBound(data);
- final Cursor oldCursor = mAdapter.swapCursor(cursor);
- updateEmptyListUi(cursor == null || cursor.getCount() == 0);
- if (mListState != null && cursor != null && oldCursor == null) {
- mRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);
- }
- }
-
- @Override
- public void setBlockedParticipantsAvailable(final boolean blockedAvailable) {
- mBlockedAvailable = blockedAvailable;
- if (mShowBlockedMenuItem != null) {
- mShowBlockedMenuItem.setVisible(blockedAvailable);
- }
- }
-
- public void updateUi() {
- mAdapter.notifyDataSetChanged();
- }
-
- @Override
- public void onPrepareOptionsMenu(final Menu menu) {
- super.onPrepareOptionsMenu(menu);
- final MenuItem startNewConversationMenuItem =
- menu.findItem(R.id.action_start_new_conversation);
- if (startNewConversationMenuItem != null) {
- // It is recommended for the Floating Action button functionality to be duplicated as a
- // menu
- AccessibilityManager accessibilityManager = (AccessibilityManager)
- getActivity().getSystemService(Context.ACCESSIBILITY_SERVICE);
- startNewConversationMenuItem.setVisible(accessibilityManager
- .isTouchExplorationEnabled());
- }
-
- final MenuItem archive = menu.findItem(R.id.action_show_archived);
- if (archive != null) {
- archive.setVisible(true);
- }
- }
-
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- if (!isAdded()) {
- // Guard against being called before we're added to the activity
- return;
- }
-
- mShowBlockedMenuItem = menu.findItem(R.id.action_show_blocked_contacts);
- if (mShowBlockedMenuItem != null) {
- mShowBlockedMenuItem.setVisible(mBlockedAvailable);
- }
- }
-
- /**
- * {@inheritDoc} from ConversationListItemView.HostInterface
- */
- @Override
- public void onConversationClicked(final ConversationListItemData conversationListItemData,
- final boolean isLongClick, final ConversationListItemView conversationView) {
- final ConversationListData listData = mListBinding.getData();
- mHost.onConversationClick(listData, conversationListItemData, isLongClick,
- conversationView);
- }
-
- /**
- * {@inheritDoc} from ConversationListItemView.HostInterface
- */
- @Override
- public boolean isConversationSelected(final String conversationId) {
- return mHost.isConversationSelected(conversationId);
- }
-
- @Override
- public boolean isSwipeAnimatable() {
- return mHost.isSwipeAnimatable();
- }
-
- // Show and hide empty list UI as needed with appropriate text based on view specifics
- private void updateEmptyListUi(final boolean isEmpty) {
- if (isEmpty) {
- int emptyListText;
- if (!mListBinding.getData().getHasFirstSyncCompleted()) {
- emptyListText = R.string.conversation_list_first_sync_text;
- } else if (mArchiveMode) {
- emptyListText = R.string.archived_conversation_list_empty_text;
- } else {
- emptyListText = R.string.conversation_list_empty_text;
- }
- mEmptyListMessageView.setTextHint(emptyListText);
- mEmptyListMessageView.setVisibility(View.VISIBLE);
- mEmptyListMessageView.setIsImageVisible(true);
- mEmptyListMessageView.setIsVerticallyCentered(true);
- } else {
- mEmptyListMessageView.setVisibility(View.GONE);
- }
- }
-
- @Override
- public List<SnackBarInteraction> getSnackBarInteractions() {
- final List<SnackBarInteraction> interactions = new ArrayList<SnackBarInteraction>(1);
- final SnackBarInteraction fabInteraction =
- new SnackBarInteraction.BasicSnackBarInteraction(mStartNewConversationButton);
- interactions.add(fabInteraction);
- return interactions;
- }
-
- private ViewPropertyAnimator getNormalizedFabAnimator() {
- return mStartNewConversationButton.animate()
- .setInterpolator(UiUtils.DEFAULT_INTERPOLATOR)
- .setDuration(getActivity().getResources().getInteger(
- R.integer.fab_animation_duration_ms));
- }
-
- public ViewPropertyAnimator dismissFab() {
- // To prevent clicking while animating.
- mStartNewConversationButton.setEnabled(false);
- final MarginLayoutParams lp =
- (MarginLayoutParams) mStartNewConversationButton.getLayoutParams();
- final float fabWidthWithLeftRightMargin = mStartNewConversationButton.getWidth()
- + lp.leftMargin + lp.rightMargin;
- final int direction = AccessibilityUtil.isLayoutRtl(mStartNewConversationButton) ? -1 : 1;
- return getNormalizedFabAnimator().translationX(direction * fabWidthWithLeftRightMargin);
- }
-
- public ViewPropertyAnimator showFab() {
- return getNormalizedFabAnimator().translationX(0).withEndAction(new Runnable() {
- @Override
- public void run() {
- // Re-enable clicks after the animation.
- mStartNewConversationButton.setEnabled(true);
- }
- });
- }
-
- public View getHeroElementForTransition() {
- return mArchiveMode ? null : mStartNewConversationButton;
- }
-
- @VisibleForAnimation
- public RecyclerView getRecyclerView() {
- return mRecyclerView;
- }
-
- @Override
- public void startFullScreenPhotoViewer(
- final Uri initialPhoto, final Rect initialPhotoBounds, final Uri photosUri) {
- UIIntents.get().launchFullScreenPhotoViewer(
- getActivity(), initialPhoto, initialPhotoBounds, photosUri);
- }
-
- @Override
- public void startFullScreenVideoViewer(final Uri videoUri) {
- UIIntents.get().launchFullScreenVideoViewer(getActivity(), videoUri);
- }
-
- @Override
- public boolean isSelectionMode() {
- return mHost != null && mHost.isSelectionMode();
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java b/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java
deleted file mode 100644
index 7525182..0000000
--- a/src/com/android/messaging/ui/conversationlist/ConversationListItemView.java
+++ /dev/null
@@ -1,643 +0,0 @@
-/*
- * 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.conversationlist;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.net.Uri;
-import android.support.v4.text.BidiFormatter;
-import android.support.v4.text.TextDirectionHeuristicsCompat;
-import android.text.TextPaint;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.View.OnLayoutChangeListener;
-import android.view.View.OnLongClickListener;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.annotation.VisibleForAnimation;
-import com.android.messaging.datamodel.MessagingContentProvider;
-import com.android.messaging.datamodel.action.UpdateConversationArchiveStatusAction;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.ui.AsyncImageView;
-import com.android.messaging.ui.AudioAttachmentView;
-import com.android.messaging.ui.ContactIconView;
-import com.android.messaging.ui.SnackBar;
-import com.android.messaging.ui.SnackBarInteraction;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-import com.android.messaging.util.Typefaces;
-import com.android.messaging.util.UiUtils;
-import com.android.messaging.util.UriUtil;
-
-import java.util.List;
-
-/**
- * The view for a single entry in a conversation list.
- */
-public class ConversationListItemView extends FrameLayout implements OnClickListener,
- OnLongClickListener, OnLayoutChangeListener {
- static final int UNREAD_SNIPPET_LINE_COUNT = 3;
- static final int NO_UNREAD_SNIPPET_LINE_COUNT = 1;
- private int mListItemReadColor;
- private int mListItemUnreadColor;
- private Typeface mListItemReadTypeface;
- private Typeface mListItemUnreadTypeface;
- private static String sPlusOneString;
- private static String sPlusNString;
-
- public interface HostInterface {
- boolean isConversationSelected(final String conversationId);
- void onConversationClicked(final ConversationListItemData conversationListItemData,
- boolean isLongClick, final ConversationListItemView conversationView);
- boolean isSwipeAnimatable();
- List<SnackBarInteraction> getSnackBarInteractions();
- void startFullScreenPhotoViewer(final Uri initialPhoto, final Rect initialPhotoBounds,
- final Uri photosUri);
- void startFullScreenVideoViewer(final Uri videoUri);
- boolean isSelectionMode();
- }
-
- private final OnClickListener fullScreenPreviewClickListener = new OnClickListener() {
- @Override
- public void onClick(final View v) {
- final String previewType = mData.getShowDraft() ?
- mData.getDraftPreviewContentType() : mData.getPreviewContentType();
- Assert.isTrue(ContentType.isImageType(previewType) ||
- ContentType.isVideoType(previewType));
-
- final Uri previewUri = mData.getShowDraft() ?
- mData.getDraftPreviewUri() : mData.getPreviewUri();
- if (ContentType.isImageType(previewType)) {
- final Uri imagesUri = mData.getShowDraft() ?
- MessagingContentProvider.buildDraftImagesUri(mData.getConversationId()) :
- MessagingContentProvider
- .buildConversationImagesUri(mData.getConversationId());
- final Rect previewImageBounds = UiUtils.getMeasuredBoundsOnScreen(v);
- mHostInterface.startFullScreenPhotoViewer(
- previewUri, previewImageBounds, imagesUri);
- } else {
- mHostInterface.startFullScreenVideoViewer(previewUri);
- }
- }
- };
-
- private final ConversationListItemData mData;
-
- private int mAnimatingCount;
- private ViewGroup mSwipeableContainer;
- private ViewGroup mCrossSwipeBackground;
- private ViewGroup mSwipeableContent;
- private TextView mConversationNameView;
- private TextView mSnippetTextView;
- private TextView mSubjectTextView;
- private TextView mTimestampTextView;
- private ContactIconView mContactIconView;
- private ImageView mContactCheckmarkView;
- private ImageView mNotificationBellView;
- private ImageView mFailedStatusIconView;
- private ImageView mCrossSwipeArchiveLeftImageView;
- private ImageView mCrossSwipeArchiveRightImageView;
- private AsyncImageView mImagePreviewView;
- private AudioAttachmentView mAudioAttachmentView;
- private HostInterface mHostInterface;
-
- public ConversationListItemView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mData = new ConversationListItemData();
- final Resources res = context.getResources();
- }
-
- @Override
- protected void onFinishInflate() {
- mSwipeableContainer = (ViewGroup) findViewById(R.id.swipeableContainer);
- mCrossSwipeBackground = (ViewGroup) findViewById(R.id.crossSwipeBackground);
- mSwipeableContent = (ViewGroup) findViewById(R.id.swipeableContent);
- mConversationNameView = (TextView) findViewById(R.id.conversation_name);
- mSnippetTextView = (TextView) findViewById(R.id.conversation_snippet);
- mSubjectTextView = (TextView) findViewById(R.id.conversation_subject);
- mTimestampTextView = (TextView) findViewById(R.id.conversation_timestamp);
- mContactIconView = (ContactIconView) findViewById(R.id.conversation_icon);
- mContactCheckmarkView = (ImageView) findViewById(R.id.conversation_checkmark);
- mNotificationBellView = (ImageView) findViewById(R.id.conversation_notification_bell);
- mFailedStatusIconView = (ImageView) findViewById(R.id.conversation_failed_status_icon);
- mCrossSwipeArchiveLeftImageView = (ImageView) findViewById(R.id.crossSwipeArchiveIconLeft);
- mCrossSwipeArchiveRightImageView =
- (ImageView) findViewById(R.id.crossSwipeArchiveIconRight);
- mImagePreviewView = (AsyncImageView) findViewById(R.id.conversation_image_preview);
- mAudioAttachmentView = (AudioAttachmentView) findViewById(R.id.audio_attachment_view);
- mConversationNameView.addOnLayoutChangeListener(this);
- mSnippetTextView.addOnLayoutChangeListener(this);
-
- final Resources resources = getContext().getResources();
- mListItemReadColor = resources.getColor(R.color.conversation_list_item_read);
- mListItemUnreadColor = resources.getColor(R.color.conversation_list_item_unread);
-
- mListItemReadTypeface = Typefaces.getRobotoNormal();
- mListItemUnreadTypeface = Typefaces.getRobotoBold();
-
- if (OsUtil.isAtLeastL()) {
- setTransitionGroup(true);
- }
- }
-
- @Override
- public void onLayoutChange(final View v, final int left, final int top, final int right,
- final int bottom, final int oldLeft, final int oldTop, final int oldRight,
- final int oldBottom) {
- if (v == mConversationNameView) {
- setConversationName();
- } else if (v == mSnippetTextView) {
- setSnippet();
- } else if (v == mSubjectTextView) {
- setSubject();
- }
- }
-
- private void setConversationName() {
- if (mData.getIsRead() || mData.getShowDraft()) {
- mConversationNameView.setTextColor(mListItemReadColor);
- mConversationNameView.setTypeface(mListItemReadTypeface);
- } else {
- mConversationNameView.setTextColor(mListItemUnreadColor);
- mConversationNameView.setTypeface(mListItemUnreadTypeface);
- }
-
- final String conversationName = mData.getName();
-
- // For group conversations, ellipsize the group members that do not fit
- final CharSequence ellipsizedName = UiUtils.commaEllipsize(
- conversationName,
- mConversationNameView.getPaint(),
- mConversationNameView.getMeasuredWidth(),
- getPlusOneString(),
- getPlusNString());
- // RTL : To format conversation name if it happens to be phone number.
- final BidiFormatter bidiFormatter = BidiFormatter.getInstance();
- final String bidiFormattedName = bidiFormatter.unicodeWrap(
- ellipsizedName.toString(),
- TextDirectionHeuristicsCompat.LTR);
-
- mConversationNameView.setText(bidiFormattedName);
- }
-
- private static String getPlusOneString() {
- if (sPlusOneString == null) {
- sPlusOneString = Factory.get().getApplicationContext().getResources()
- .getString(R.string.plus_one);
- }
- return sPlusOneString;
- }
-
- private static String getPlusNString() {
- if (sPlusNString == null) {
- sPlusNString = Factory.get().getApplicationContext().getResources()
- .getString(R.string.plus_n);
- }
- return sPlusNString;
- }
-
- private void setSubject() {
- final String subjectText = mData.getShowDraft() ?
- mData.getDraftSubject() :
- MmsUtils.cleanseMmsSubject(getContext().getResources(), mData.getSubject());
- if (!TextUtils.isEmpty(subjectText)) {
- final String subjectPrepend = getResources().getString(R.string.subject_label);
- mSubjectTextView.setText(TextUtils.concat(subjectPrepend, subjectText));
- mSubjectTextView.setVisibility(VISIBLE);
- } else {
- mSubjectTextView.setVisibility(GONE);
- }
- }
-
- private void setSnippet() {
- mSnippetTextView.setText(getSnippetText());
- }
-
- // Resource Ids of content descriptions prefixes for different message status.
- private static final int [][][] sPrimaryContentDescriptions = {
- // 1:1 conversation
- {
- // Incoming message
- {
- R.string.one_on_one_incoming_failed_message_prefix,
- R.string.one_on_one_incoming_successful_message_prefix
- },
- // Outgoing message
- {
- R.string.one_on_one_outgoing_failed_message_prefix,
- R.string.one_on_one_outgoing_successful_message_prefix,
- R.string.one_on_one_outgoing_draft_message_prefix,
- R.string.one_on_one_outgoing_sending_message_prefix,
- }
- },
-
- // Group conversation
- {
- // Incoming message
- {
- R.string.group_incoming_failed_message_prefix,
- R.string.group_incoming_successful_message_prefix,
- },
- // Outgoing message
- {
- R.string.group_outgoing_failed_message_prefix,
- R.string.group_outgoing_successful_message_prefix,
- R.string.group_outgoing_draft_message_prefix,
- R.string.group_outgoing_sending_message_prefix,
- }
- }
- };
-
- // Resource Id of the secondary part of the content description for an edge case of a message
- // which is in both draft status and failed status.
- private static final int sSecondaryContentDescription =
- R.string.failed_message_content_description;
-
- // 1:1 versus group
- private static final int CONV_TYPE_ONE_ON_ONE_INDEX = 0;
- private static final int CONV_TYPE_ONE_GROUP_INDEX = 1;
- // Direction
- private static final int DIRECTION_INCOMING_INDEX = 0;
- private static final int DIRECTION_OUTGOING_INDEX = 1;
- // Message status
- private static final int MESSAGE_STATUS_FAILED_INDEX = 0;
- private static final int MESSAGE_STATUS_SUCCESSFUL_INDEX = 1;
- private static final int MESSAGE_STATUS_DRAFT_INDEX = 2;
- private static final int MESSAGE_STATUS_SENDING_INDEX = 3;
-
- private static final int WIDTH_FOR_ACCESSIBLE_CONVERSATION_NAME = 600;
-
- public static String buildContentDescription(final Resources resources,
- final ConversationListItemData data, final TextPaint conversationNameViewPaint) {
- int messageStatusIndex;
- boolean outgoingSnippet = data.getIsMessageTypeOutgoing() || data.getShowDraft();
- if (outgoingSnippet) {
- if (data.getShowDraft()) {
- messageStatusIndex = MESSAGE_STATUS_DRAFT_INDEX;
- } else if (data.getIsSendRequested()) {
- messageStatusIndex = MESSAGE_STATUS_SENDING_INDEX;
- } else {
- messageStatusIndex = data.getIsFailedStatus() ? MESSAGE_STATUS_FAILED_INDEX
- : MESSAGE_STATUS_SUCCESSFUL_INDEX;
- }
- } else {
- messageStatusIndex = data.getIsFailedStatus() ? MESSAGE_STATUS_FAILED_INDEX
- : MESSAGE_STATUS_SUCCESSFUL_INDEX;
- }
-
- int resId = sPrimaryContentDescriptions
- [data.getIsGroup() ? CONV_TYPE_ONE_GROUP_INDEX : CONV_TYPE_ONE_ON_ONE_INDEX]
- [outgoingSnippet ? DIRECTION_OUTGOING_INDEX : DIRECTION_INCOMING_INDEX]
- [messageStatusIndex];
-
- final String snippetText = data.getShowDraft() ?
- data.getDraftSnippetText() : data.getSnippetText();
-
- final String conversationName = data.getName();
- String senderOrConvName = outgoingSnippet ? conversationName : data.getSnippetSenderName();
-
- String primaryContentDescription = resources.getString(resId, senderOrConvName,
- snippetText == null ? "" : snippetText,
- data.getFormattedTimestamp(),
- // This is used only for incoming group messages
- conversationName);
- String contentDescription = primaryContentDescription;
-
- // An edge case : for an outgoing message, it might be in both draft status and
- // failed status.
- if (outgoingSnippet && data.getShowDraft() && data.getIsFailedStatus()) {
- StringBuilder contentDescriptionBuilder = new StringBuilder();
- contentDescriptionBuilder.append(primaryContentDescription);
-
- String secondaryContentDescription =
- resources.getString(sSecondaryContentDescription);
- contentDescriptionBuilder.append(" ");
- contentDescriptionBuilder.append(secondaryContentDescription);
- contentDescription = contentDescriptionBuilder.toString();
- }
- return contentDescription;
- }
-
- /**
- * Fills in the data associated with this view.
- *
- * @param cursor The cursor from a ConversationList that this view is in, pointing to its
- * entry.
- */
- public void bind(final Cursor cursor, final HostInterface hostInterface) {
- // Update our UI model
- mHostInterface = hostInterface;
- mData.bind(cursor);
-
- resetAnimatingState();
-
- mSwipeableContainer.setOnClickListener(this);
- mSwipeableContainer.setOnLongClickListener(this);
-
- final Resources resources = getContext().getResources();
-
- int color;
- final int maxLines;
- final Typeface typeface;
- final int typefaceStyle = mData.getShowDraft() ? Typeface.ITALIC : Typeface.NORMAL;
- final String snippetText = getSnippetText();
-
- if (mData.getIsRead() || mData.getShowDraft()) {
- maxLines = TextUtils.isEmpty(snippetText) ? 0 : NO_UNREAD_SNIPPET_LINE_COUNT;
- color = mListItemReadColor;
- typeface = mListItemReadTypeface;
- } else {
- maxLines = TextUtils.isEmpty(snippetText) ? 0 : UNREAD_SNIPPET_LINE_COUNT;
- color = mListItemUnreadColor;
- typeface = mListItemUnreadTypeface;
- }
-
- mSnippetTextView.setMaxLines(maxLines);
- mSnippetTextView.setTextColor(color);
- mSnippetTextView.setTypeface(typeface, typefaceStyle);
- mSubjectTextView.setTextColor(color);
- mSubjectTextView.setTypeface(typeface, typefaceStyle);
-
- setSnippet();
- setConversationName();
- setSubject();
- setContentDescription(buildContentDescription(resources, mData,
- mConversationNameView.getPaint()));
-
- final boolean isDefaultSmsApp = PhoneUtils.getDefault().isDefaultSmsApp();
- // don't show the error state unless we're the default sms app
- if (mData.getIsFailedStatus() && isDefaultSmsApp) {
- mTimestampTextView.setTextColor(resources.getColor(R.color.conversation_list_error));
- mTimestampTextView.setTypeface(mListItemReadTypeface, typefaceStyle);
- int failureMessageId = R.string.message_status_download_failed;
- if (mData.getIsMessageTypeOutgoing()) {
- failureMessageId = MmsUtils.mapRawStatusToErrorResourceId(mData.getMessageStatus(),
- mData.getMessageRawTelephonyStatus());
- }
- mTimestampTextView.setText(resources.getString(failureMessageId));
- } else if (mData.getShowDraft()
- || mData.getMessageStatus() == MessageData.BUGLE_STATUS_OUTGOING_DRAFT
- // also check for unknown status which we get because sometimes the conversation
- // row is left with a latest_message_id of a no longer existing message and
- // therefore the join values come back as null (or in this case zero).
- || mData.getMessageStatus() == MessageData.BUGLE_STATUS_UNKNOWN) {
- mTimestampTextView.setTextColor(mListItemReadColor);
- mTimestampTextView.setTypeface(mListItemReadTypeface, typefaceStyle);
- mTimestampTextView.setText(resources.getString(
- R.string.conversation_list_item_view_draft_message));
- } else {
- mTimestampTextView.setTextColor(mListItemReadColor);
- mTimestampTextView.setTypeface(mListItemReadTypeface, typefaceStyle);
- final String formattedTimestamp = mData.getFormattedTimestamp();
- if (mData.getIsSendRequested()) {
- mTimestampTextView.setText(R.string.message_status_sending);
- } else {
- mTimestampTextView.setText(formattedTimestamp);
- }
- }
-
- final boolean isSelected = mHostInterface.isConversationSelected(mData.getConversationId());
- setSelected(isSelected);
- Uri iconUri = null;
- int contactIconVisibility = GONE;
- int checkmarkVisiblity = GONE;
- int failStatusVisiblity = GONE;
- if (isSelected) {
- checkmarkVisiblity = VISIBLE;
- } else {
- contactIconVisibility = VISIBLE;
- // Only show the fail icon if it is not a group conversation.
- // And also require that we be the default sms app.
- if (mData.getIsFailedStatus() && !mData.getIsGroup() && isDefaultSmsApp) {
- failStatusVisiblity = VISIBLE;
- }
- }
- if (mData.getIcon() != null) {
- iconUri = Uri.parse(mData.getIcon());
- }
- mContactIconView.setImageResourceUri(iconUri, mData.getParticipantContactId(),
- mData.getParticipantLookupKey(), mData.getOtherParticipantNormalizedDestination());
- mContactIconView.setVisibility(contactIconVisibility);
- mContactIconView.setOnLongClickListener(this);
- mContactIconView.setClickable(!mHostInterface.isSelectionMode());
- mContactIconView.setLongClickable(!mHostInterface.isSelectionMode());
-
- mContactCheckmarkView.setVisibility(checkmarkVisiblity);
- mFailedStatusIconView.setVisibility(failStatusVisiblity);
-
- final Uri previewUri = mData.getShowDraft() ?
- mData.getDraftPreviewUri() : mData.getPreviewUri();
- final String previewContentType = mData.getShowDraft() ?
- mData.getDraftPreviewContentType() : mData.getPreviewContentType();
- OnClickListener previewClickListener = null;
- Uri previewImageUri = null;
- int previewImageVisibility = GONE;
- int audioPreviewVisiblity = GONE;
- if (previewUri != null && !TextUtils.isEmpty(previewContentType)) {
- if (ContentType.isAudioType(previewContentType)) {
- mAudioAttachmentView.bind(previewUri, false);
- audioPreviewVisiblity = VISIBLE;
- } else if (ContentType.isVideoType(previewContentType)) {
- previewImageUri = UriUtil.getUriForResourceId(
- getContext(), R.drawable.ic_preview_play);
- previewClickListener = fullScreenPreviewClickListener;
- previewImageVisibility = VISIBLE;
- } else if (ContentType.isImageType(previewContentType)) {
- previewImageUri = previewUri;
- previewClickListener = fullScreenPreviewClickListener;
- previewImageVisibility = VISIBLE;
- }
- }
-
- final int imageSize = resources.getDimensionPixelSize(
- R.dimen.conversation_list_image_preview_size);
- mImagePreviewView.setImageResourceId(
- new UriImageRequestDescriptor(previewImageUri, imageSize, imageSize,
- true /* allowCompression */, false /* isStatic */, false /*cropToCircle*/,
- ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
- ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */));
- mImagePreviewView.setOnLongClickListener(this);
- mImagePreviewView.setVisibility(previewImageVisibility);
- mImagePreviewView.setOnClickListener(previewClickListener);
- mAudioAttachmentView.setOnLongClickListener(this);
- mAudioAttachmentView.setVisibility(audioPreviewVisiblity);
-
- final int notificationBellVisiblity = mData.getNotificationEnabled() ? GONE : VISIBLE;
- mNotificationBellView.setVisibility(notificationBellVisiblity);
- }
-
- public boolean isSwipeAnimatable() {
- return mHostInterface.isSwipeAnimatable();
- }
-
- @VisibleForAnimation
- public float getSwipeTranslationX() {
- return mSwipeableContainer.getTranslationX();
- }
-
- @VisibleForAnimation
- public void setSwipeTranslationX(final float translationX) {
- mSwipeableContainer.setTranslationX(translationX);
- if (translationX == 0) {
- mCrossSwipeBackground.setVisibility(View.GONE);
- mCrossSwipeArchiveLeftImageView.setVisibility(GONE);
- mCrossSwipeArchiveRightImageView.setVisibility(GONE);
-
- mSwipeableContainer.setBackgroundColor(Color.TRANSPARENT);
- } else {
- mCrossSwipeBackground.setVisibility(View.VISIBLE);
- if (translationX > 0) {
- mCrossSwipeArchiveLeftImageView.setVisibility(VISIBLE);
- mCrossSwipeArchiveRightImageView.setVisibility(GONE);
- } else {
- mCrossSwipeArchiveLeftImageView.setVisibility(GONE);
- mCrossSwipeArchiveRightImageView.setVisibility(VISIBLE);
- }
- mSwipeableContainer.setBackgroundResource(R.drawable.swipe_shadow_drag);
- }
- }
-
- public void onSwipeComplete() {
- final String conversationId = mData.getConversationId();
- UpdateConversationArchiveStatusAction.archiveConversation(conversationId);
-
- final Runnable undoRunnable = new Runnable() {
- @Override
- public void run() {
- UpdateConversationArchiveStatusAction.unarchiveConversation(conversationId);
- }
- };
- final String message = getResources().getString(R.string.archived_toast_message, 1);
- UiUtils.showSnackBar(getContext(), getRootView(), message, undoRunnable,
- SnackBar.Action.SNACK_BAR_UNDO,
- mHostInterface.getSnackBarInteractions());
- }
-
- private void setShortAndLongClickable(final boolean clickable) {
- setClickable(clickable);
- setLongClickable(clickable);
- }
-
- private void resetAnimatingState() {
- mAnimatingCount = 0;
- setShortAndLongClickable(true);
- setSwipeTranslationX(0);
- }
-
- /**
- * Notifies this view that it is undergoing animation. This view should disable its click
- * targets.
- *
- * The animating counter is used to reset the swipe controller when the counter becomes 0. A
- * positive counter also makes the view not clickable.
- */
- public final void setAnimating(final boolean animating) {
- final int oldAnimatingCount = mAnimatingCount;
- if (animating) {
- mAnimatingCount++;
- } else {
- mAnimatingCount--;
- if (mAnimatingCount < 0) {
- mAnimatingCount = 0;
- }
- }
-
- if (mAnimatingCount == 0) {
- // New count is 0. All animations ended.
- setShortAndLongClickable(true);
- } else if (oldAnimatingCount == 0) {
- // New count is > 0. Waiting for some animations to end.
- setShortAndLongClickable(false);
- }
- }
-
- public boolean isAnimating() {
- return mAnimatingCount > 0;
- }
-
- /**
- * {@inheritDoc} from OnClickListener
- */
- @Override
- public void onClick(final View v) {
- processClick(v, false);
- }
-
- /**
- * {@inheritDoc} from OnLongClickListener
- */
- @Override
- public boolean onLongClick(final View v) {
- return processClick(v, true);
- }
-
- private boolean processClick(final View v, final boolean isLongClick) {
- Assert.isTrue(v == mSwipeableContainer || v == mContactIconView || v == mImagePreviewView);
- Assert.notNull(mData.getName());
-
- if (mHostInterface != null) {
- mHostInterface.onConversationClicked(mData, isLongClick, this);
- return true;
- }
- return false;
- }
-
- public View getSwipeableContent() {
- return mSwipeableContent;
- }
-
- public View getContactIconView() {
- return mContactIconView;
- }
-
- private String getSnippetText() {
- String snippetText = mData.getShowDraft() ?
- mData.getDraftSnippetText() : mData.getSnippetText();
- final String previewContentType = mData.getShowDraft() ?
- mData.getDraftPreviewContentType() : mData.getPreviewContentType();
- if (TextUtils.isEmpty(snippetText)) {
- Resources resources = getResources();
- // Use the attachment type as a snippet so the preview doesn't look odd
- if (ContentType.isAudioType(previewContentType)) {
- snippetText = resources.getString(R.string.conversation_list_snippet_audio_clip);
- } else if (ContentType.isImageType(previewContentType)) {
- snippetText = resources.getString(R.string.conversation_list_snippet_picture);
- } else if (ContentType.isVideoType(previewContentType)) {
- snippetText = resources.getString(R.string.conversation_list_snippet_video);
- } else if (ContentType.isVCardType(previewContentType)) {
- snippetText = resources.getString(R.string.conversation_list_snippet_vcard);
- }
- }
- return snippetText;
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java b/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java
deleted file mode 100644
index 4988259..0000000
--- a/src/com/android/messaging/ui/conversationlist/ConversationListSwipeHelper.java
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * 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.conversationlist;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.support.v4.view.ViewCompat;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.OnItemTouchListener;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-import com.android.messaging.R;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.UiUtils;
-
-/**
- * Animation and touch helper class for Conversation List swipe.
- */
-public class ConversationListSwipeHelper implements OnItemTouchListener {
- private static final int UNIT_SECONDS = 1000;
- private static final boolean ANIMATING = true;
-
- private static final float ERROR_FACTOR_MULTIPLIER = 1.2f;
- private static final float PERCENTAGE_OF_WIDTH_TO_DISMISS = 0.4f;
- private static final float FLING_PERCENTAGE_OF_WIDTH_TO_DISMISS = 0.05f;
-
- private static final int SWIPE_DIRECTION_NONE = 0;
- private static final int SWIPE_DIRECTION_LEFT = 1;
- private static final int SWIPE_DIRECTION_RIGHT = 2;
-
- private final RecyclerView mRecyclerView;
- private final long mDefaultRestoreAnimationDuration;
- private final long mDefaultDismissAnimationDuration;
- private final long mMaxTranslationAnimationDuration;
- private final int mTouchSlop;
- private final int mMinimumFlingVelocity;
- private final int mMaximumFlingVelocity;
-
- /* Valid throughout a single gesture. */
- private VelocityTracker mVelocityTracker;
- private float mInitialX;
- private float mInitialY;
- private boolean mIsSwiping;
- private ConversationListItemView mListItemView;
-
- public ConversationListSwipeHelper(final RecyclerView recyclerView) {
- mRecyclerView = recyclerView;
-
- final Context context = mRecyclerView.getContext();
- final Resources res = context.getResources();
- mDefaultRestoreAnimationDuration = res.getInteger(R.integer.swipe_duration_ms);
- mDefaultDismissAnimationDuration = res.getInteger(R.integer.swipe_duration_ms);
- mMaxTranslationAnimationDuration = res.getInteger(R.integer.swipe_duration_ms);
-
- final ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
- mTouchSlop = viewConfiguration.getScaledPagingTouchSlop();
- mMaximumFlingVelocity = Math.min(
- viewConfiguration.getScaledMaximumFlingVelocity(),
- res.getInteger(R.integer.swipe_max_fling_velocity_px_per_s));
- mMinimumFlingVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
- }
-
- @Override
- public boolean onInterceptTouchEvent(final RecyclerView recyclerView, final MotionEvent event) {
- if (event.getPointerCount() > 1) {
- // Ignore subsequent pointers.
- return false;
- }
-
- // We are not yet tracking a swipe gesture. Begin detection by spying on
- // touch events bubbling down to our children.
- final int action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- if (!hasGestureSwipeTarget()) {
- onGestureStart();
-
- mVelocityTracker.addMovement(event);
- mInitialX = event.getX();
- mInitialY = event.getY();
-
- final View viewAtPoint = mRecyclerView.findChildViewUnder(mInitialX, mInitialY);
- final ConversationListItemView child = (ConversationListItemView) viewAtPoint;
- if (viewAtPoint instanceof ConversationListItemView &&
- child != null && child.isSwipeAnimatable()) {
- // Begin detecting swipe on the target for the rest of the gesture.
- mListItemView = child;
- if (mListItemView.isAnimating()) {
- mListItemView = null;
- }
- } else {
- mListItemView = null;
- }
- }
- break;
- case MotionEvent.ACTION_MOVE:
- if (hasValidGestureSwipeTarget()) {
- mVelocityTracker.addMovement(event);
-
- final int historicalCount = event.getHistorySize();
- // First consume the historical events, then consume the current ones.
- for (int i = 0; i < historicalCount + 1; i++) {
- float currX;
- float currY;
- if (i < historicalCount) {
- currX = event.getHistoricalX(i);
- currY = event.getHistoricalY(i);
- } else {
- currX = event.getX();
- currY = event.getY();
- }
- final float deltaX = currX - mInitialX;
- final float deltaY = currY - mInitialY;
- final float absDeltaX = Math.abs(deltaX);
- final float absDeltaY = Math.abs(deltaY);
-
- if (!mIsSwiping && absDeltaY > mTouchSlop
- && absDeltaY > (ERROR_FACTOR_MULTIPLIER * absDeltaX)) {
- // Stop detecting swipe for the remainder of this gesture.
- onGestureEnd();
- return false;
- }
-
- if (absDeltaX > mTouchSlop) {
- // Swipe detected. Return true so we can handle the gesture in
- // onTouchEvent.
- mIsSwiping = true;
-
- // We don't want to suddenly jump the slop distance.
- mInitialX = event.getX();
- mInitialY = event.getY();
-
- onSwipeGestureStart(mListItemView);
- return true;
- }
- }
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- if (hasGestureSwipeTarget()) {
- onGestureEnd();
- }
- break;
- }
-
- // Start intercepting touch events from children if we detect a swipe.
- return mIsSwiping;
- }
-
- @Override
- public void onTouchEvent(final RecyclerView recyclerView, final MotionEvent event) {
- // We should only be here if we intercepted the touch due to swipe.
- Assert.isTrue(mIsSwiping);
-
- // We are now tracking a swipe gesture.
- mVelocityTracker.addMovement(event);
-
- final int action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_OUTSIDE:
- case MotionEvent.ACTION_MOVE:
- if (hasValidGestureSwipeTarget()) {
- mListItemView.setSwipeTranslationX(event.getX() - mInitialX);
- }
- break;
- case MotionEvent.ACTION_UP:
- if (hasValidGestureSwipeTarget()) {
- final float maxVelocity = mMaximumFlingVelocity;
- mVelocityTracker.computeCurrentVelocity(UNIT_SECONDS, maxVelocity);
- final float velocityX = getLastComputedXVelocity();
-
- final float translationX = mListItemView.getSwipeTranslationX();
-
- int swipeDirection = SWIPE_DIRECTION_NONE;
- if (translationX != 0) {
- swipeDirection =
- translationX > 0 ? SWIPE_DIRECTION_RIGHT : SWIPE_DIRECTION_LEFT;
- } else if (velocityX != 0) {
- swipeDirection =
- velocityX > 0 ? SWIPE_DIRECTION_RIGHT : SWIPE_DIRECTION_LEFT;
- }
-
- final boolean fastEnough = isTargetSwipedFastEnough();
- final boolean farEnough = isTargetSwipedFarEnough();
-
- final boolean shouldDismiss = (fastEnough || farEnough);
-
- if (shouldDismiss) {
- if (fastEnough) {
- animateDismiss(mListItemView, velocityX);
- } else {
- animateDismiss(mListItemView, swipeDirection);
- }
- } else {
- animateRestore(mListItemView, velocityX);
- }
-
- onSwipeGestureEnd(mListItemView,
- shouldDismiss ? swipeDirection : SWIPE_DIRECTION_NONE);
- } else {
- onGestureEnd();
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- if (hasValidGestureSwipeTarget()) {
- animateRestore(mListItemView, 0f);
- onSwipeGestureEnd(mListItemView, SWIPE_DIRECTION_NONE);
- } else {
- onGestureEnd();
- }
- break;
- }
- }
-
-
- @Override
- public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
- }
-
- /**
- * We have started to intercept a series of touch events.
- */
- private void onGestureStart() {
- mIsSwiping = false;
- // Work around bug in RecyclerView that sends two identical ACTION_DOWN
- // events to #onInterceptTouchEvent.
- if (mVelocityTracker == null) {
- mVelocityTracker = VelocityTracker.obtain();
- }
- mVelocityTracker.clear();
- }
-
- /**
- * The series of touch events has been detected as a swipe.
- *
- * Now that the gesture is a swipe, we will begin translating the view of the
- * given viewHolder.
- */
- private void onSwipeGestureStart(final ConversationListItemView itemView) {
- mRecyclerView.getParent().requestDisallowInterceptTouchEvent(true);
- setHardwareAnimatingLayerType(itemView, ANIMATING);
- itemView.setAnimating(true);
- }
-
- /**
- * The current swipe gesture is complete.
- */
- private void onSwipeGestureEnd(final ConversationListItemView itemView,
- final int swipeDirection) {
- if (swipeDirection == SWIPE_DIRECTION_RIGHT || swipeDirection == SWIPE_DIRECTION_LEFT) {
- itemView.onSwipeComplete();
- }
-
- // Balances out onSwipeGestureStart.
- itemView.setAnimating(false);
-
- onGestureEnd();
- }
-
- /**
- * The series of touch events has ended in an {@link MotionEvent#ACTION_UP}
- * or {@link MotionEvent#ACTION_CANCEL}.
- */
- private void onGestureEnd() {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- mIsSwiping = false;
- mListItemView = null;
- }
-
- /**
- * A swipe animation has started.
- */
- private void onSwipeAnimationStart(final ConversationListItemView itemView) {
- // Disallow interactions.
- itemView.setAnimating(true);
- ViewCompat.setHasTransientState(itemView, true);
- setHardwareAnimatingLayerType(itemView, ANIMATING);
- }
-
- /**
- * The swipe animation has ended.
- */
- private void onSwipeAnimationEnd(final ConversationListItemView itemView) {
- // Restore interactions.
- itemView.setAnimating(false);
- ViewCompat.setHasTransientState(itemView, false);
- setHardwareAnimatingLayerType(itemView, !ANIMATING);
- }
-
- /**
- * Animate the dismissal of the given item. The given velocityX is taken into consideration for
- * the animation duration. Whether the item is dismissed to the left or right is dependent on
- * the given velocityX.
- */
- private void animateDismiss(final ConversationListItemView itemView, final float velocityX) {
- Assert.isTrue(velocityX != 0);
- final int direction = velocityX > 0 ? SWIPE_DIRECTION_RIGHT : SWIPE_DIRECTION_LEFT;
- animateDismiss(itemView, direction, velocityX);
- }
-
- /**
- * Animate the dismissal of the given item. The velocityX is assumed to be 0.
- */
- private void animateDismiss(final ConversationListItemView itemView, final int swipeDirection) {
- animateDismiss(itemView, swipeDirection, 0f);
- }
-
- /**
- * Animate the dismissal of the given item.
- */
- private void animateDismiss(final ConversationListItemView itemView,
- final int swipeDirection, final float velocityX) {
- Assert.isTrue(swipeDirection != SWIPE_DIRECTION_NONE);
-
- onSwipeAnimationStart(itemView);
-
- final float animateTo = (swipeDirection == SWIPE_DIRECTION_RIGHT) ?
- mRecyclerView.getWidth() : -mRecyclerView.getWidth();
- final long duration;
- if (velocityX != 0) {
- final float deltaX = animateTo - itemView.getSwipeTranslationX();
- duration = calculateTranslationDuration(deltaX, velocityX);
- } else {
- duration = mDefaultDismissAnimationDuration;
- }
-
- final ObjectAnimator animator = getSwipeTranslationXAnimator(
- itemView, animateTo, duration, UiUtils.DEFAULT_INTERPOLATOR);
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- onSwipeAnimationEnd(itemView);
- }
- });
- animator.start();
- }
-
- /**
- * Animate the bounce back of the given item.
- */
- private void animateRestore(final ConversationListItemView itemView,
- final float velocityX) {
- onSwipeAnimationStart(itemView);
-
- final float translationX = itemView.getSwipeTranslationX();
- final long duration;
- if (velocityX != 0 // Has velocity.
- && velocityX > 0 != translationX > 0) { // Right direction.
- duration = calculateTranslationDuration(translationX, velocityX);
- } else {
- duration = mDefaultRestoreAnimationDuration;
- }
- final ObjectAnimator animator = getSwipeTranslationXAnimator(
- itemView, 0f, duration, UiUtils.DEFAULT_INTERPOLATOR);
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(final Animator animation) {
- onSwipeAnimationEnd(itemView);
- }
- });
- animator.start();
- }
-
- /**
- * Create and start an animator that animates the given view's translationX
- * from its current value to the value given by animateTo.
- */
- private ObjectAnimator getSwipeTranslationXAnimator(final ConversationListItemView itemView,
- final float animateTo, final long duration, final TimeInterpolator interpolator) {
- final ObjectAnimator animator =
- ObjectAnimator.ofFloat(itemView, "swipeTranslationX", animateTo);
- animator.setDuration(duration);
- animator.setInterpolator(interpolator);
- return animator;
- }
-
- /**
- * Determine if the swipe has enough velocity to be dismissed.
- */
- private boolean isTargetSwipedFastEnough() {
- final float velocityX = getLastComputedXVelocity();
- final float velocityY = mVelocityTracker.getYVelocity();
- final float minVelocity = mMinimumFlingVelocity;
- final float translationX = mListItemView.getSwipeTranslationX();
- final float width = mListItemView.getWidth();
- return (Math.abs(velocityX) > minVelocity) // Fast enough.
- && (Math.abs(velocityX) > Math.abs(velocityY)) // Not unintentional.
- && (velocityX > 0) == (translationX > 0) // Right direction.
- && Math.abs(translationX) >
- FLING_PERCENTAGE_OF_WIDTH_TO_DISMISS * width; // Enough movement.
- }
-
- /**
- * Only used during a swipe gesture. Determine if the swipe has enough distance to be
- * dismissed.
- */
- private boolean isTargetSwipedFarEnough() {
- final float velocityX = getLastComputedXVelocity();
-
- final float translationX = mListItemView.getSwipeTranslationX();
- final float width = mListItemView.getWidth();
-
- return (velocityX >= 0) == (translationX > 0) // Right direction.
- && Math.abs(translationX) >
- PERCENTAGE_OF_WIDTH_TO_DISMISS * width; // Enough movement.
- }
-
- private long calculateTranslationDuration(final float deltaPosition, final float velocity) {
- Assert.isTrue(velocity != 0);
- final float durationInSeconds = Math.abs(deltaPosition / velocity);
- return Math.min((int) (durationInSeconds * UNIT_SECONDS), mMaxTranslationAnimationDuration);
- }
-
- private boolean hasGestureSwipeTarget() {
- return mListItemView != null;
- }
-
- private boolean hasValidGestureSwipeTarget() {
- return hasGestureSwipeTarget() && mListItemView.getParent() == mRecyclerView;
- }
-
- /**
- * Enable a hardware layer for the it view and build that layer.
- */
- private void setHardwareAnimatingLayerType(final ConversationListItemView itemView,
- final boolean animating) {
- if (animating) {
- itemView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- if (itemView.getWindowToken() != null) {
- itemView.buildLayer();
- }
- } else {
- itemView.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- }
-
- private float getLastComputedXVelocity() {
- return mVelocityTracker.getXVelocity();
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/conversationlist/ForwardMessageActivity.java b/src/com/android/messaging/ui/conversationlist/ForwardMessageActivity.java
deleted file mode 100644
index 61e3640..0000000
--- a/src/com/android/messaging/ui/conversationlist/ForwardMessageActivity.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.conversationlist;
-
-import android.app.Fragment;
-import android.os.Bundle;
-
-import com.android.messaging.datamodel.data.ConversationListData;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.ui.BaseBugleActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.conversationlist.ConversationListFragment.ConversationListFragmentHost;
-import com.android.messaging.util.Assert;
-
-/**
- * An activity that lets the user forward a SMS/MMS message by picking from a conversation in the
- * conversation list.
- */
-public class ForwardMessageActivity extends BaseBugleActivity
- implements ConversationListFragmentHost {
- private MessageData mDraftMessage;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- final ConversationListFragment fragment =
- ConversationListFragment.createForwardMessageConversationListFragment();
- getFragmentManager().beginTransaction().add(android.R.id.content, fragment).commit();
- mDraftMessage = getIntent().getParcelableExtra(UIIntents.UI_INTENT_EXTRA_DRAFT_DATA);
- }
-
- @Override
- public void onAttachFragment(final Fragment fragment) {
- Assert.isTrue(fragment instanceof ConversationListFragment);
- final ConversationListFragment clf = (ConversationListFragment) fragment;
- clf.setHost(this);
- }
-
- @Override
- public void onConversationClick(final ConversationListData listData,
- final ConversationListItemData conversationListItemData,
- final boolean isLongClick, final ConversationListItemView converastionView) {
- UIIntents.get().launchConversationActivity(
- this, conversationListItemData.getConversationId(), mDraftMessage);
- }
-
- @Override
- public void onCreateConversationClick() {
- UIIntents.get().launchCreateNewConversationActivity(this, mDraftMessage);
- }
-
- @Override
- public boolean isConversationSelected(final String conversationId) {
- return false;
- }
-
- @Override
- public boolean isSwipeAnimatable() {
- return false;
- }
-
- @Override
- public boolean isSelectionMode() {
- return false;
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/MultiSelectActionModeCallback.java b/src/com/android/messaging/ui/conversationlist/MultiSelectActionModeCallback.java
deleted file mode 100644
index bfeec51..0000000
--- a/src/com/android/messaging/ui/conversationlist/MultiSelectActionModeCallback.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * 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.conversationlist;
-
-import android.support.v4.util.ArrayMap;
-import android.text.TextUtils;
-import android.view.ActionMode;
-import android.view.ActionMode.Callback;
-import android.view.Menu;
-import android.view.MenuItem;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ConversationListData;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.util.Assert;
-
-import java.util.Collection;
-import java.util.HashSet;
-
-public class MultiSelectActionModeCallback implements Callback {
- private HashSet<String> mBlockedSet;
-
- public interface Listener {
- void onActionBarDelete(Collection<SelectedConversation> conversations);
- void onActionBarArchive(Iterable<SelectedConversation> conversations,
- boolean isToArchive);
- void onActionBarNotification(Iterable<SelectedConversation> conversations,
- boolean isNotificationOn);
- void onActionBarAddContact(final SelectedConversation conversation);
- void onActionBarBlock(final SelectedConversation conversation);
- void onActionBarHome();
- }
-
- static class SelectedConversation {
- public final String conversationId;
- public final long timestamp;
- public final String icon;
- public final String otherParticipantNormalizedDestination;
- public final CharSequence participantLookupKey;
- public final boolean isGroup;
- public final boolean isArchived;
- public final boolean notificationEnabled;
- public SelectedConversation(ConversationListItemData data) {
- conversationId = data.getConversationId();
- timestamp = data.getTimestamp();
- icon = data.getIcon();
- otherParticipantNormalizedDestination = data.getOtherParticipantNormalizedDestination();
- participantLookupKey = data.getParticipantLookupKey();
- isGroup = data.getIsGroup();
- isArchived = data.getIsArchived();
- notificationEnabled = data.getNotificationEnabled();
- }
- }
-
- private final ArrayMap<String, SelectedConversation> mSelectedConversations;
-
- private Listener mListener;
- private MenuItem mArchiveMenuItem;
- private MenuItem mUnarchiveMenuItem;
- private MenuItem mAddContactMenuItem;
- private MenuItem mBlockMenuItem;
- private MenuItem mNotificationOnMenuItem;
- private MenuItem mNotificationOffMenuItem;
- private boolean mHasInflated;
-
- public MultiSelectActionModeCallback(final Listener listener) {
- mListener = listener;
- mSelectedConversations = new ArrayMap<>();
-
- }
-
- @Override
- public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
- actionMode.getMenuInflater().inflate(R.menu.conversation_list_fragment_select_menu, menu);
- mArchiveMenuItem = menu.findItem(R.id.action_archive);
- mUnarchiveMenuItem = menu.findItem(R.id.action_unarchive);
- mAddContactMenuItem = menu.findItem(R.id.action_add_contact);
- mBlockMenuItem = menu.findItem(R.id.action_block);
- mNotificationOffMenuItem = menu.findItem(R.id.action_notification_off);
- mNotificationOnMenuItem = menu.findItem(R.id.action_notification_on);
- mHasInflated = true;
- updateActionIconsVisiblity();
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
- return true;
- }
-
- @Override
- public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
- switch(menuItem.getItemId()) {
- case R.id.action_delete:
- mListener.onActionBarDelete(mSelectedConversations.values());
- return true;
- case R.id.action_archive:
- mListener.onActionBarArchive(mSelectedConversations.values(), true);
- return true;
- case R.id.action_unarchive:
- mListener.onActionBarArchive(mSelectedConversations.values(), false);
- return true;
- case R.id.action_notification_off:
- mListener.onActionBarNotification(mSelectedConversations.values(), false);
- return true;
- case R.id.action_notification_on:
- mListener.onActionBarNotification(mSelectedConversations.values(), true);
- return true;
- case R.id.action_add_contact:
- Assert.isTrue(mSelectedConversations.size() == 1);
- mListener.onActionBarAddContact(mSelectedConversations.valueAt(0));
- return true;
- case R.id.action_block:
- Assert.isTrue(mSelectedConversations.size() == 1);
- mListener.onActionBarBlock(mSelectedConversations.valueAt(0));
- return true;
- case android.R.id.home:
- mListener.onActionBarHome();
- return true;
- default:
- return false;
- }
- }
-
- @Override
- public void onDestroyActionMode(ActionMode actionMode) {
- mListener = null;
- mSelectedConversations.clear();
- mHasInflated = false;
- }
-
- public void toggleSelect(final ConversationListData listData,
- final ConversationListItemData conversationListItemData) {
- Assert.notNull(conversationListItemData);
- mBlockedSet = listData.getBlockedParticipants();
- final String id = conversationListItemData.getConversationId();
- if (mSelectedConversations.containsKey(id)) {
- mSelectedConversations.remove(id);
- } else {
- mSelectedConversations.put(id, new SelectedConversation(conversationListItemData));
- }
-
- if (mSelectedConversations.isEmpty()) {
- mListener.onActionBarHome();
- } else {
- updateActionIconsVisiblity();
- }
- }
-
- public boolean isSelected(final String selectedId) {
- return mSelectedConversations.containsKey(selectedId);
- }
-
- private void updateActionIconsVisiblity() {
- if (!mHasInflated) {
- return;
- }
-
- if (mSelectedConversations.size() == 1) {
- final SelectedConversation conversation = mSelectedConversations.valueAt(0);
- // The look up key is a key given to us by contacts app, so if we have a look up key,
- // we know that the participant is already in contacts.
- final boolean isInContacts = !TextUtils.isEmpty(conversation.participantLookupKey);
- mAddContactMenuItem.setVisible(!conversation.isGroup && !isInContacts);
- // ParticipantNormalizedDestination is always null for group conversations.
- final String otherParticipant = conversation.otherParticipantNormalizedDestination;
- mBlockMenuItem.setVisible(otherParticipant != null
- && !mBlockedSet.contains(otherParticipant));
- } else {
- mBlockMenuItem.setVisible(false);
- mAddContactMenuItem.setVisible(false);
- }
-
- boolean hasCurrentlyArchived = false;
- boolean hasCurrentlyUnarchived = false;
- boolean hasCurrentlyOnNotification = false;
- boolean hasCurrentlyOffNotification = false;
- final Iterable<SelectedConversation> conversations = mSelectedConversations.values();
- for (final SelectedConversation conversation : conversations) {
- if (conversation.notificationEnabled) {
- hasCurrentlyOnNotification = true;
- } else {
- hasCurrentlyOffNotification = true;
- }
-
- if (conversation.isArchived) {
- hasCurrentlyArchived = true;
- } else {
- hasCurrentlyUnarchived = true;
- }
-
- // If we found at least one of each example we don't need to keep looping.
- if (hasCurrentlyOffNotification && hasCurrentlyOnNotification &&
- hasCurrentlyArchived && hasCurrentlyUnarchived) {
- break;
- }
- }
- // If we have notification off conversations we show on button, if we have notification on
- // conversation we show off button. We can show both if we have a mixture.
- mNotificationOffMenuItem.setVisible(hasCurrentlyOnNotification);
- mNotificationOnMenuItem.setVisible(hasCurrentlyOffNotification);
-
- mArchiveMenuItem.setVisible(hasCurrentlyUnarchived);
- mUnarchiveMenuItem.setVisible(hasCurrentlyArchived);
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java b/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java
deleted file mode 100644
index ef7fcef..0000000
--- a/src/com/android/messaging/ui/conversationlist/ShareIntentActivity.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * 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.conversationlist;
-
-import android.app.Fragment;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.media.MediaMetadataRetriever;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.TextUtils;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.datamodel.data.MessageData;
-import com.android.messaging.datamodel.data.PendingAttachmentData;
-import com.android.messaging.ui.BaseBugleActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.MediaMetadataRetrieverWrapper;
-
-import java.io.IOException;
-import java.util.ArrayList;
-
-public class ShareIntentActivity extends BaseBugleActivity implements
- ShareIntentFragment.HostInterface {
-
- private MessageData mDraftMessage;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- final Intent intent = getIntent();
- if (Intent.ACTION_SEND.equals(intent.getAction()) &&
- (!TextUtils.isEmpty(intent.getStringExtra("address")) ||
- !TextUtils.isEmpty(intent.getStringExtra(Intent.EXTRA_EMAIL)))) {
- // This is really more like a SENDTO intent because a destination is supplied.
- // It's coming through the SEND intent because that's the intent that is used
- // when invoking the chooser with Intent.createChooser().
- final Intent convIntent = UIIntents.get().getLaunchConversationActivityIntent(this);
- // Copy the important items from the original intent to the new intent.
- convIntent.putExtras(intent);
- convIntent.setAction(Intent.ACTION_SENDTO);
- convIntent.setDataAndType(intent.getData(), intent.getType());
- // We have to fire off the intent and finish before trying to show the fragment,
- // otherwise we get some flashing.
- startActivity(convIntent);
- finish();
- return;
- }
- new ShareIntentFragment().show(getFragmentManager(), "ShareIntentFragment");
- }
-
- @Override
- public void onAttachFragment(final Fragment fragment) {
- final Intent intent = getIntent();
- final String action = intent.getAction();
- if (Intent.ACTION_SEND.equals(action)) {
- final Uri contentUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
- final String contentType = extractContentType(contentUri, intent.getType());
- if (LogUtil.isLoggable(LogUtil.BUGLE_TAG, LogUtil.DEBUG)) {
- LogUtil.d(LogUtil.BUGLE_TAG, String.format(
- "onAttachFragment: contentUri=%s, intent.getType()=%s, inferredType=%s",
- contentUri, intent.getType(), contentType));
- }
- if (ContentType.TEXT_PLAIN.equals(contentType)) {
- final String sharedText = intent.getStringExtra(Intent.EXTRA_TEXT);
- if (sharedText != null) {
- mDraftMessage = MessageData.createSharedMessage(sharedText);
- } else {
- mDraftMessage = null;
- }
- } else if (ContentType.isImageType(contentType) ||
- ContentType.isVCardType(contentType) ||
- ContentType.isAudioType(contentType) ||
- ContentType.isVideoType(contentType)) {
- if (contentUri != null) {
- mDraftMessage = MessageData.createSharedMessage(null);
- addSharedImagePartToDraft(contentType, contentUri);
- } else {
- mDraftMessage = null;
- }
- } else {
- // Unsupported content type.
- Assert.fail("Unsupported shared content type for " + contentUri + ": " + contentType
- + " (" + intent.getType() + ")");
- }
- } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
- final String contentType = intent.getType();
- if (ContentType.isImageType(contentType)) {
- // Handle sharing multiple images.
- final ArrayList<Uri> imageUris = intent.getParcelableArrayListExtra(
- Intent.EXTRA_STREAM);
- if (imageUris != null && imageUris.size() > 0) {
- mDraftMessage = MessageData.createSharedMessage(null);
- for (final Uri imageUri : imageUris) {
- final String actualContentType = extractContentType(imageUri, contentType);
- addSharedImagePartToDraft(actualContentType, imageUri);
- }
- } else {
- mDraftMessage = null;
- }
- } else {
- // Unsupported content type.
- Assert.fail("Unsupported shared content type: " + contentType);
- }
- } else {
- // Unsupported action.
- Assert.fail("Unsupported action type for sharing: " + action);
- }
- }
-
- private static String extractContentType(final Uri uri, final String contentType) {
- if (uri == null) {
- return contentType;
- }
- // First try looking at file extension. This is less reliable in some ways but it's
- // recommended by
- // https://developer.android.com/training/secure-file-sharing/retrieve-info.html
- // Some implementations of MediaMetadataRetriever get things horribly
- // wrong for common formats such as jpeg (reports as video/ffmpeg)
- final ContentResolver resolver = Factory.get().getApplicationContext().getContentResolver();
- final String typeFromExtension = resolver.getType(uri);
- if (typeFromExtension != null) {
- return typeFromExtension;
- }
- final MediaMetadataRetrieverWrapper retriever = new MediaMetadataRetrieverWrapper();
- try {
- retriever.setDataSource(uri);
- final String extractedType = retriever.extractMetadata(
- MediaMetadataRetriever.METADATA_KEY_MIMETYPE);
- if (extractedType != null) {
- return extractedType;
- }
- } catch (final IOException e) {
- LogUtil.i(LogUtil.BUGLE_TAG, "Could not determine type of " + uri, e);
- } finally {
- retriever.release();
- }
- return contentType;
- }
-
- private void addSharedImagePartToDraft(final String contentType, final Uri imageUri) {
- mDraftMessage.addPart(PendingAttachmentData.createPendingAttachmentData(contentType,
- imageUri));
- }
-
- @Override
- public void onConversationClick(final ConversationListItemData conversationListItemData) {
- UIIntents.get().launchConversationActivity(
- this, conversationListItemData.getConversationId(), mDraftMessage);
- finish();
- }
-
- @Override
- public void onCreateConversationClick() {
- UIIntents.get().launchCreateNewConversationActivity(this, mDraftMessage);
- finish();
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/ShareIntentAdapter.java b/src/com/android/messaging/ui/conversationlist/ShareIntentAdapter.java
deleted file mode 100644
index e894145..0000000
--- a/src/com/android/messaging/ui/conversationlist/ShareIntentAdapter.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * 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.conversationlist;
-
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.Uri;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.data.PersonItemData;
-import com.android.messaging.ui.CursorRecyclerAdapter;
-import com.android.messaging.ui.PersonItemView;
-import com.android.messaging.ui.PersonItemView.PersonItemViewListener;
-import com.android.messaging.util.PhoneUtils;
-
-/**
- * Turn conversation rows into PeopleItemViews
- */
-public class ShareIntentAdapter
- extends CursorRecyclerAdapter<ShareIntentAdapter.ShareIntentViewHolder> {
-
- public interface HostInterface {
- void onConversationClicked(final ConversationListItemData conversationListItemData);
- }
-
- private final HostInterface mHostInterface;
-
- public ShareIntentAdapter(final Context context, final Cursor cursor,
- final HostInterface hostInterface) {
- super(context, cursor, 0);
- mHostInterface = hostInterface;
- setHasStableIds(true);
- }
-
- @Override
- public void bindViewHolder(final ShareIntentViewHolder holder, final Context context,
- final Cursor cursor) {
- holder.bind(cursor);
- }
-
- @Override
- public ShareIntentViewHolder createViewHolder(final Context context,
- final ViewGroup parent, final int viewType) {
- final PersonItemView itemView = (PersonItemView) LayoutInflater.from(context).inflate(
- R.layout.people_list_item_view, null);
- return new ShareIntentViewHolder(itemView);
- }
-
- /**
- * Holds a PersonItemView and keeps it synced with a ConversationListItemData.
- */
- public class ShareIntentViewHolder extends RecyclerView.ViewHolder implements
- PersonItemView.PersonItemViewListener {
- private final ConversationListItemData mData = new ConversationListItemData();
- private final PersonItemData mItemData = new PersonItemData() {
- @Override
- public Uri getAvatarUri() {
- return mData.getIcon() == null ? null : Uri.parse(mData.getIcon());
- }
-
- @Override
- public String getDisplayName() {
- return mData.getName();
- }
-
- @Override
- public String getDetails() {
- final String conversationName = mData.getName();
- final String conversationPhone = PhoneUtils.getDefault().formatForDisplay(
- mData.getOtherParticipantNormalizedDestination());
- if (conversationPhone == null || conversationPhone.equals(conversationName)) {
- return null;
- }
- return conversationPhone;
- }
-
- @Override
- public Intent getClickIntent() {
- return null;
- }
-
- @Override
- public long getContactId() {
- return ParticipantData.PARTICIPANT_CONTACT_ID_NOT_RESOLVED;
- }
-
- @Override
- public String getLookupKey() {
- return null;
- }
-
- @Override
- public String getNormalizedDestination() {
- return null;
- }
- };
-
- public ShareIntentViewHolder(final PersonItemView itemView) {
- super(itemView);
- itemView.setListener(this);
- }
-
- public void bind(Cursor cursor) {
- mData.bind(cursor);
- ((PersonItemView) itemView).bind(mItemData);
- }
-
- @Override
- public void onPersonClicked(PersonItemData data) {
- mHostInterface.onConversationClicked(mData);
- }
-
- @Override
- public boolean onPersonLongClicked(PersonItemData data) {
- return false;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversationlist/ShareIntentFragment.java b/src/com/android/messaging/ui/conversationlist/ShareIntentFragment.java
deleted file mode 100644
index bc549ea..0000000
--- a/src/com/android/messaging/ui/conversationlist/ShareIntentFragment.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * 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.conversationlist;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.AlertDialog.Builder;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.ConversationListData;
-import com.android.messaging.datamodel.data.ConversationListItemData;
-import com.android.messaging.datamodel.data.ConversationListData.ConversationListDataListener;
-import com.android.messaging.ui.ListEmptyView;
-import com.android.messaging.datamodel.DataModel;
-
-/**
- * Allow user to pick conversation to which an incoming attachment will be shared.
- */
-public class ShareIntentFragment extends DialogFragment implements ConversationListDataListener,
- ShareIntentAdapter.HostInterface {
- public static final String HIDE_NEW_CONVERSATION_BUTTON_KEY = "hide_conv_button_key";
-
- public interface HostInterface {
- public void onConversationClick(final ConversationListItemData conversationListItemData);
- public void onCreateConversationClick();
- }
-
- private final Binding<ConversationListData> mListBinding = BindingBase.createBinding(this);
- private RecyclerView mRecyclerView;
- private ListEmptyView mEmptyListMessageView;
- private ShareIntentAdapter mAdapter;
- private HostInterface mHost;
- private boolean mDismissed;
-
- /**
- * {@inheritDoc} from Fragment
- */
- @Override
- public Dialog onCreateDialog(final Bundle bundle) {
- final Activity activity = getActivity();
- final LayoutInflater inflater = activity.getLayoutInflater();
- View view = inflater.inflate(R.layout.share_intent_conversation_list_view, null);
- mEmptyListMessageView = (ListEmptyView) view.findViewById(R.id.no_conversations_view);
- mEmptyListMessageView.setImageHint(R.drawable.ic_oobe_conv_list);
- // The default behavior for default layout param generation by LinearLayoutManager is to
- // provide width and height of WRAP_CONTENT, but this is not desirable for
- // ShareIntentFragment; the view in each row should be a width of MATCH_PARENT so that
- // the entire row is tappable.
- final LinearLayoutManager manager = new LinearLayoutManager(activity) {
- @Override
- public RecyclerView.LayoutParams generateDefaultLayoutParams() {
- return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- }
- };
- mListBinding.getData().init(getLoaderManager(), mListBinding);
- mAdapter = new ShareIntentAdapter(activity, null, this);
- mRecyclerView = (RecyclerView) view.findViewById(android.R.id.list);
- mRecyclerView.setLayoutManager(manager);
- mRecyclerView.setHasFixedSize(true);
- mRecyclerView.setAdapter(mAdapter);
- final Builder dialogBuilder = new AlertDialog.Builder(activity)
- .setView(view)
- .setTitle(R.string.share_intent_activity_label);
-
- final Bundle arguments = getArguments();
- if (arguments == null || !arguments.getBoolean(HIDE_NEW_CONVERSATION_BUTTON_KEY)) {
- dialogBuilder.setPositiveButton(R.string.share_new_message, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mDismissed = true;
- mHost.onCreateConversationClick();
- }
- });
- }
- return dialogBuilder.setNegativeButton(R.string.share_cancel, null)
- .create();
- }
-
- @Override
- public void onDismiss(DialogInterface dialog) {
- if (!mDismissed) {
- final Activity activity = getActivity();
- if (activity != null) {
- activity.finish();
- }
- }
- }
-
- /**
- * {@inheritDoc} from Fragment
- */
- @Override
- public void onDestroy() {
- super.onDestroy();
- mListBinding.unbind();
- }
-
- @Override
- public void onAttach(final Activity activity) {
- super.onAttach(activity);
- if (activity instanceof HostInterface) {
- mHost = (HostInterface) activity;
- }
- mListBinding.bind(DataModel.get().createConversationListData(activity, this, false));
- }
-
- @Override
- public void onConversationListCursorUpdated(final ConversationListData data,
- final Cursor cursor) {
- mListBinding.ensureBound(data);
- mAdapter.swapCursor(cursor);
- updateEmptyListUi(cursor == null || cursor.getCount() == 0);
- }
-
- /**
- * {@inheritDoc} from SharIntentItemView.HostInterface
- */
- @Override
- public void onConversationClicked(final ConversationListItemData conversationListItemData) {
- mHost.onConversationClick(conversationListItemData);
- }
-
- // Show and hide empty list UI as needed with appropriate text based on view specifics
- private void updateEmptyListUi(final boolean isEmpty) {
- if (isEmpty) {
- mEmptyListMessageView.setTextHint(R.string.conversation_list_empty_text);
- mEmptyListMessageView.setVisibility(View.VISIBLE);
- } else {
- mEmptyListMessageView.setVisibility(View.GONE);
- }
- }
-
- @Override
- public void setBlockedParticipantsAvailable(boolean blockedAvailable) {
- }
-}
diff --git a/src/com/android/messaging/ui/conversationsettings/CopyContactDetailDialog.java b/src/com/android/messaging/ui/conversationsettings/CopyContactDetailDialog.java
deleted file mode 100644
index d727001..0000000
--- a/src/com/android/messaging/ui/conversationsettings/CopyContactDetailDialog.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.conversationsettings;
-
-import android.app.AlertDialog;
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.util.AccessibilityUtil;
-
-public class CopyContactDetailDialog implements DialogInterface.OnClickListener {
-
- private final Context mContext;
- private final String mContactDetail;
-
- public CopyContactDetailDialog(final Context context, final String contactDetail) {
- mContext = context;
- mContactDetail = contactDetail;
- }
-
- public void show() {
- new AlertDialog.Builder(mContext)
- .setView(createBodyView())
- .setTitle(R.string.copy_to_clipboard_dialog_title)
- .setPositiveButton(R.string.copy_to_clipboard, this)
- .show();
- }
-
- @Override
- public void onClick(final DialogInterface dialog, final int which) {
- final ClipboardManager clipboard =
- (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
- clipboard.setPrimaryClip(ClipData.newPlainText(null /* label */, mContactDetail));
- }
-
- private View createBodyView() {
- LayoutInflater inflater = (LayoutInflater) mContext
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- TextView textView = (TextView) inflater.inflate(R.layout.copy_contact_dialog_view, null,
- false);
- textView.setText(mContactDetail);
- final String vocalizedDisplayName = AccessibilityUtil.getVocalizedPhoneNumber(
- mContext.getResources(), mContactDetail);
- textView.setContentDescription(vocalizedDisplayName);
- return textView;
- }
-}
diff --git a/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsActivity.java b/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsActivity.java
deleted file mode 100644
index f017328..0000000
--- a/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsActivity.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.conversationsettings;
-
-import android.app.Fragment;
-import android.os.Bundle;
-import android.view.MenuItem;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.Assert;
-
-/**
- * Shows a list of participants in a conversation.
- */
-public class PeopleAndOptionsActivity extends BugleActionBarActivity {
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.people_and_options_activity);
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- }
-
- @Override
- public void onAttachFragment(final Fragment fragment) {
- if (fragment instanceof PeopleAndOptionsFragment) {
- final String conversationId =
- getIntent().getStringExtra(UIIntents.UI_INTENT_EXTRA_CONVERSATION_ID);
- Assert.notNull(conversationId);
- final PeopleAndOptionsFragment peopleAndOptionsFragment =
- (PeopleAndOptionsFragment) fragment;
- peopleAndOptionsFragment.setConversationId(conversationId);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- // Treat the home press as back press so that when we go back to
- // ConversationActivity, it doesn't lose its original intent (conversation id etc.)
- onBackPressed();
- return true;
-
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsFragment.java b/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsFragment.java
deleted file mode 100644
index b86d952..0000000
--- a/src/com/android/messaging/ui/conversationsettings/PeopleAndOptionsFragment.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * 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.conversationsettings;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.app.Fragment;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.media.RingtoneManager;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.provider.Settings;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.BaseAdapter;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.data.ParticipantListItemData;
-import com.android.messaging.datamodel.data.PeopleAndOptionsData;
-import com.android.messaging.datamodel.data.PeopleAndOptionsData.PeopleAndOptionsDataListener;
-import com.android.messaging.datamodel.data.PeopleOptionsItemData;
-import com.android.messaging.datamodel.data.PersonItemData;
-import com.android.messaging.ui.CompositeAdapter;
-import com.android.messaging.ui.PersonItemView;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.ui.conversation.ConversationActivity;
-import com.android.messaging.util.Assert;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Shows a list of participants of a conversation and displays options.
- */
-public class PeopleAndOptionsFragment extends Fragment
- implements PeopleAndOptionsDataListener, PeopleOptionsItemView.HostInterface {
- private ListView mListView;
- private OptionsListAdapter mOptionsListAdapter;
- private PeopleListAdapter mPeopleListAdapter;
- private final Binding<PeopleAndOptionsData> mBinding =
- BindingBase.createBinding(this);
-
- private static final int REQUEST_CODE_RINGTONE_PICKER = 1000;
-
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mBinding.getData().init(getLoaderManager(), mBinding);
- }
-
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedInstanceState) {
- final View view = inflater.inflate(R.layout.people_and_options_fragment, container, false);
- mListView = (ListView) view.findViewById(android.R.id.list);
- mPeopleListAdapter = new PeopleListAdapter(getActivity());
- mOptionsListAdapter = new OptionsListAdapter();
- final CompositeAdapter compositeAdapter = new CompositeAdapter(getActivity());
- compositeAdapter.addPartition(new PeopleAndOptionsPartition(mOptionsListAdapter,
- R.string.general_settings_title, false));
- compositeAdapter.addPartition(new PeopleAndOptionsPartition(mPeopleListAdapter,
- R.string.participant_list_title, true));
- mListView.setAdapter(compositeAdapter);
- return view;
- }
-
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_RINGTONE_PICKER) {
- final Parcelable pick = data.getParcelableExtra(
- RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
- final String pickedUri = pick == null ? "" : pick.toString();
- mBinding.getData().setConversationNotificationSound(mBinding, pickedUri);
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mBinding.unbind();
- }
-
- public void setConversationId(final String conversationId) {
- Assert.isTrue(getView() == null);
- Assert.notNull(conversationId);
- mBinding.bind(DataModel.get().createPeopleAndOptionsData(conversationId, getActivity(),
- this));
- }
-
- @Override
- public void onOptionsCursorUpdated(final PeopleAndOptionsData data, final Cursor cursor) {
- Assert.isTrue(cursor == null || cursor.getCount() == 1);
- mBinding.ensureBound(data);
- mOptionsListAdapter.swapCursor(cursor);
- }
-
- @Override
- public void onParticipantsListLoaded(final PeopleAndOptionsData data,
- final List<ParticipantData> participants) {
- mBinding.ensureBound(data);
- mPeopleListAdapter.updateParticipants(participants);
- final ParticipantData otherParticipant = participants.size() == 1 ?
- participants.get(0) : null;
- mOptionsListAdapter.setOtherParticipant(otherParticipant);
- }
-
- @Override
- public void onOptionsItemViewClicked(final PeopleOptionsItemData item,
- final boolean isChecked) {
- switch (item.getItemId()) {
- case PeopleOptionsItemData.SETTING_NOTIFICATION_ENABLED:
- mBinding.getData().enableConversationNotifications(mBinding, isChecked);
- break;
-
- case PeopleOptionsItemData.SETTING_NOTIFICATION_SOUND_URI:
- final Intent ringtonePickerIntent = UIIntents.get().getRingtonePickerIntent(
- getString(R.string.notification_sound_pref_title),
- item.getRingtoneUri(), Settings.System.DEFAULT_NOTIFICATION_URI,
- RingtoneManager.TYPE_NOTIFICATION);
- startActivityForResult(ringtonePickerIntent, REQUEST_CODE_RINGTONE_PICKER);
- break;
-
- case PeopleOptionsItemData.SETTING_NOTIFICATION_VIBRATION:
- mBinding.getData().enableConversationNotificationVibration(mBinding,
- isChecked);
- break;
-
- case PeopleOptionsItemData.SETTING_BLOCKED:
- if (item.getOtherParticipant().isBlocked()) {
- mBinding.getData().setDestinationBlocked(mBinding, false);
- break;
- }
- final Resources res = getResources();
- final Activity activity = getActivity();
- new AlertDialog.Builder(activity)
- .setTitle(res.getString(R.string.block_confirmation_title,
- item.getOtherParticipant().getDisplayDestination()))
- .setMessage(res.getString(R.string.block_confirmation_message))
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface arg0, int arg1) {
- mBinding.getData().setDestinationBlocked(mBinding, true);
- activity.setResult(ConversationActivity.FINISH_RESULT_CODE);
- activity.finish();
- }
- })
- .create()
- .show();
- break;
- }
- }
-
- /**
- * A simple adapter that takes a conversation metadata cursor and binds
- * PeopleAndOptionsItemViews to individual COLUMNS of the first cursor record. (Note
- * that this is not a CursorAdapter because it treats individual columns of the cursor as
- * separate options to display for the conversation, e.g. notification settings).
- */
- private class OptionsListAdapter extends BaseAdapter {
- private Cursor mOptionsCursor;
- private ParticipantData mOtherParticipantData;
-
- public Cursor swapCursor(final Cursor newCursor) {
- final Cursor oldCursor = mOptionsCursor;
- if (newCursor != oldCursor) {
- mOptionsCursor = newCursor;
- notifyDataSetChanged();
- }
- return oldCursor;
- }
-
- public void setOtherParticipant(final ParticipantData participantData) {
- if (mOtherParticipantData != participantData) {
- mOtherParticipantData = participantData;
- notifyDataSetChanged();
- }
- }
-
- @Override
- public int getCount() {
- int count = PeopleOptionsItemData.SETTINGS_COUNT;
- if (mOtherParticipantData == null) {
- count--;
- }
- return mOptionsCursor == null ? 0 : count;
- }
-
- @Override
- public Object getItem(final int position) {
- return null;
- }
-
- @Override
- public long getItemId(final int position) {
- return 0;
- }
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parent) {
- final PeopleOptionsItemView itemView;
- if (convertView != null && convertView instanceof PeopleOptionsItemView) {
- itemView = (PeopleOptionsItemView) convertView;
- } else {
- final LayoutInflater inflater = (LayoutInflater) getActivity()
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- itemView = (PeopleOptionsItemView)
- inflater.inflate(R.layout.people_options_item_view, parent, false);
- }
- mOptionsCursor.moveToFirst();
- itemView.bind(mOptionsCursor, position, mOtherParticipantData,
- PeopleAndOptionsFragment.this);
- return itemView;
- }
- }
-
- /**
- * An adapter that takes a list of ParticipantData and displays them as a list of
- * ParticipantListItemViews.
- */
- private class PeopleListAdapter extends ArrayAdapter<ParticipantData> {
- public PeopleListAdapter(final Context context) {
- super(context, R.layout.people_list_item_view, new ArrayList<ParticipantData>());
- }
-
- public void updateParticipants(final List<ParticipantData> newList) {
- clear();
- addAll(newList);
- notifyDataSetChanged();
- }
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parent) {
- PersonItemView itemView;
- final ParticipantData item = getItem(position);
- if (convertView != null && convertView instanceof PersonItemView) {
- itemView = (PersonItemView) convertView;
- } else {
- final LayoutInflater inflater = (LayoutInflater) getContext()
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- itemView = (PersonItemView) inflater.inflate(R.layout.people_list_item_view, parent,
- false);
- }
- final ParticipantListItemData itemData =
- DataModel.get().createParticipantListItemData(item);
- itemView.bind(itemData);
-
- // Any click on the row should have the same effect as clicking the avatar icon
- final PersonItemView itemViewClosure = itemView;
- itemView.setListener(new PersonItemView.PersonItemViewListener() {
- @Override
- public void onPersonClicked(final PersonItemData data) {
- itemViewClosure.performClickOnAvatar();
- }
-
- @Override
- public boolean onPersonLongClicked(final PersonItemData data) {
- if (mBinding.isBound()) {
- final CopyContactDetailDialog dialog = new CopyContactDetailDialog(
- getContext(), data.getDetails());
- dialog.show();
- return true;
- }
- return false;
- }
- });
- return itemView;
- }
- }
-
- /**
- * Represents a partition/section in the People & Options list (e.g. "general options" and
- * "people in this conversation" sections).
- */
- private class PeopleAndOptionsPartition extends CompositeAdapter.Partition {
- private final int mHeaderResId;
- private final boolean mNeedDivider;
-
- public PeopleAndOptionsPartition(final BaseAdapter adapter, final int headerResId,
- final boolean needDivider) {
- super(true /* showIfEmpty */, true /* hasHeader */, adapter);
- mHeaderResId = headerResId;
- mNeedDivider = needDivider;
- }
-
- @Override
- public View getHeaderView(final View convertView, final ViewGroup parentView) {
- View view = null;
- if (convertView != null && convertView.getId() == R.id.people_and_options_header) {
- view = convertView;
- } else {
- view = LayoutInflater.from(getActivity()).inflate(
- R.layout.people_and_options_section_header, parentView, false);
- }
- final TextView text = (TextView) view.findViewById(R.id.header_text);
- final View divider = view.findViewById(R.id.divider);
- text.setText(mHeaderResId);
- divider.setVisibility(mNeedDivider ? View.VISIBLE : View.GONE);
- return view;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/conversationsettings/PeopleOptionsItemView.java b/src/com/android/messaging/ui/conversationsettings/PeopleOptionsItemView.java
deleted file mode 100644
index 42ecfeb..0000000
--- a/src/com/android/messaging/ui/conversationsettings/PeopleOptionsItemView.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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.conversationsettings;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v7.widget.SwitchCompat;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.data.PeopleOptionsItemData;
-import com.android.messaging.util.Assert;
-
-/**
- * The view for a single entry in the options section of people & options activity.
- */
-public class PeopleOptionsItemView extends LinearLayout {
- /**
- * Implemented by the host of this view that handles options click event.
- */
- public interface HostInterface {
- void onOptionsItemViewClicked(PeopleOptionsItemData item, boolean isChecked);
- }
-
- private TextView mTitle;
- private TextView mSubtitle;
- private SwitchCompat mSwitch;
- private final PeopleOptionsItemData mData;
- private HostInterface mHostInterface;
-
- public PeopleOptionsItemView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mData = DataModel.get().createPeopleOptionsItemData(context);
- }
-
- @Override
- protected void onFinishInflate () {
- mTitle = (TextView) findViewById(R.id.title);
- mSubtitle = (TextView) findViewById(R.id.subtitle);
- mSwitch = (SwitchCompat) findViewById(R.id.switch_button);
- setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View v) {
- mHostInterface.onOptionsItemViewClicked(mData, !mData.getChecked());
- }
- });
- }
-
- public void bind(final Cursor cursor, final int columnIndex, ParticipantData otherParticipant,
- final HostInterface hostInterface) {
- Assert.isTrue(columnIndex < PeopleOptionsItemData.SETTINGS_COUNT && columnIndex >= 0);
- mData.bind(cursor, otherParticipant, columnIndex);
- mHostInterface = hostInterface;
-
- mTitle.setText(mData.getTitle());
- final String subtitle = mData.getSubtitle();
- if (TextUtils.isEmpty(subtitle)) {
- mSubtitle.setVisibility(GONE);
- } else {
- mSubtitle.setVisibility(VISIBLE);
- mSubtitle.setText(subtitle);
- }
-
- if (mData.getCheckable()) {
- mSwitch.setVisibility(VISIBLE);
- mSwitch.setChecked(mData.getChecked());
- } else {
- mSwitch.setVisibility(GONE);
- }
-
- final boolean enabled = mData.getEnabled();
- if (enabled != isEnabled()) {
- mTitle.setEnabled(enabled);
- mSubtitle.setEnabled(enabled);
- mSwitch.setEnabled(enabled);
- setAlpha(enabled ? 1.0f : 0.5f);
- setEnabled(enabled);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/debug/DebugMmsConfigActivity.java b/src/com/android/messaging/ui/debug/DebugMmsConfigActivity.java
deleted file mode 100644
index 485dcf7..0000000
--- a/src/com/android/messaging/ui/debug/DebugMmsConfigActivity.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.debug;
-
-import android.os.Bundle;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.BaseBugleActivity;
-
-/**
- * Show list of all MmsConfig key/value pairs and allow editing.
- */
-public class DebugMmsConfigActivity extends BaseBugleActivity {
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.debug_mmsconfig_activity);
- }
-}
diff --git a/src/com/android/messaging/ui/debug/DebugMmsConfigFragment.java b/src/com/android/messaging/ui/debug/DebugMmsConfigFragment.java
deleted file mode 100644
index 7c54db5..0000000
--- a/src/com/android/messaging/ui/debug/DebugMmsConfigFragment.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * 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.debug;
-
-import android.app.Fragment;
-import android.content.Context;
-import android.os.Bundle;
-import android.telephony.SubscriptionInfo;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.ArrayAdapter;
-import android.widget.BaseAdapter;
-import android.widget.ListView;
-import android.widget.Spinner;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.ui.debug.DebugMmsConfigItemView.MmsConfigItemListener;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.PhoneUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Show list of all MmsConfig key/value pairs and allow editing.
- */
-public class DebugMmsConfigFragment extends Fragment {
- @Override
- public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
- final Bundle savedInstanceState) {
- final View fragmentView = inflater.inflate(R.layout.mms_config_debug_fragment, container,
- false);
- final ListView listView = (ListView) fragmentView.findViewById(android.R.id.list);
- final Spinner spinner = (Spinner) fragmentView.findViewById(R.id.sim_selector);
- final Integer[] subIdArray = getActiveSubIds();
- ArrayAdapter<Integer> spinnerAdapter = new ArrayAdapter<Integer>(getActivity(),
- android.R.layout.simple_spinner_item, subIdArray);
- spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- spinner.setAdapter(spinnerAdapter);
- spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- listView.setAdapter(new MmsConfigAdapter(getActivity(), subIdArray[position]));
-
- final int[] mccmnc = PhoneUtils.get(subIdArray[position]).getMccMnc();
- // Set the title with the mcc/mnc
- final TextView title = (TextView) fragmentView.findViewById(R.id.sim_title);
- title.setText("(" + mccmnc[0] + "/" + mccmnc[1] + ") " +
- getActivity().getString(R.string.debug_sub_id_spinner_text));
- }
-
- @Override
- public void onNothingSelected(AdapterView<?> parent) {
- }
- });
-
- return fragmentView;
- }
-
- public static Integer[] getActiveSubIds() {
- if (!OsUtil.isAtLeastL_MR1()) {
- return new Integer[] { ParticipantData.DEFAULT_SELF_SUB_ID };
- }
- final List<SubscriptionInfo> subRecords =
- PhoneUtils.getDefault().toLMr1().getActiveSubscriptionInfoList();
- if (subRecords == null) {
- return new Integer[0];
- }
- final Integer[] retArray = new Integer[subRecords.size()];
- for (int i = 0; i < subRecords.size(); i++) {
- retArray[i] = subRecords.get(i).getSubscriptionId();
- }
- return retArray;
- }
-
- private class MmsConfigAdapter extends BaseAdapter implements
- DebugMmsConfigItemView.MmsConfigItemListener {
- private final LayoutInflater mInflater;
- private final List<String> mKeys;
- private final MmsConfig mMmsConfig;
-
- public MmsConfigAdapter(Context context, int subId) {
- mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mMmsConfig = MmsConfig.get(subId);
- mKeys = new ArrayList<>(mMmsConfig.keySet());
- Collections.sort(mKeys);
- }
-
- @Override
- public View getView(final int position, final View convertView, final ViewGroup parent) {
- final DebugMmsConfigItemView view;
- if (convertView != null && convertView instanceof DebugMmsConfigItemView) {
- view = (DebugMmsConfigItemView) convertView;
- } else {
- view = (DebugMmsConfigItemView) mInflater.inflate(
- R.layout.debug_mmsconfig_item_view, parent, false);
- }
- final String key = mKeys.get(position);
- view.bind(key,
- MmsConfig.getKeyType(key),
- String.valueOf(mMmsConfig.getValue(key)),
- this);
- return view;
- }
-
- @Override
- public void onValueChanged(String key, String keyType, String value) {
- mMmsConfig.update(key, value, keyType);
- notifyDataSetChanged();
- }
-
- @Override
- public int getCount() {
- return mKeys.size();
- }
-
- @Override
- public Object getItem(int position) {
- return mKeys.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/debug/DebugMmsConfigItemView.java b/src/com/android/messaging/ui/debug/DebugMmsConfigItemView.java
deleted file mode 100644
index 7b899c0..0000000
--- a/src/com/android/messaging/ui/debug/DebugMmsConfigItemView.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * 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.debug;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnShowListener;
-import android.text.InputType;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.CompoundButton;
-import android.widget.CompoundButton.OnCheckedChangeListener;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.Switch;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.util.LogUtil;
-
-public class DebugMmsConfigItemView extends LinearLayout implements OnClickListener,
- OnCheckedChangeListener, DialogInterface.OnClickListener {
-
- public interface MmsConfigItemListener {
- void onValueChanged(String key, String keyType, String value);
- }
-
- private TextView mTitle;
- private TextView mTextValue;
- private Switch mSwitch;
- private String mKey;
- private String mKeyType;
- private MmsConfigItemListener mListener;
- private EditText mEditText;
-
- public DebugMmsConfigItemView(Context context, AttributeSet attributeSet) {
- super(context, attributeSet);
- }
-
- @Override
- protected void onFinishInflate () {
- mTitle = (TextView) findViewById(R.id.title);
- mTextValue = (TextView) findViewById(R.id.text_value);
- mSwitch = (Switch) findViewById(R.id.switch_button);
- setOnClickListener(this);
- mSwitch.setOnCheckedChangeListener(this);
- }
-
- public void bind(final String key, final String keyType, final String value,
- final MmsConfigItemListener listener) {
- mListener = listener;
- mKey = key;
- mKeyType = keyType;
- mTitle.setText(key);
-
- switch (keyType) {
- case MmsConfig.KEY_TYPE_BOOL:
- mSwitch.setVisibility(View.VISIBLE);
- mTextValue.setVisibility(View.GONE);
- mSwitch.setChecked(Boolean.valueOf(value));
- break;
- case MmsConfig.KEY_TYPE_STRING:
- case MmsConfig.KEY_TYPE_INT:
- mTextValue.setVisibility(View.VISIBLE);
- mSwitch.setVisibility(View.GONE);
- mTextValue.setText(value);
- break;
- default:
- mTextValue.setVisibility(View.GONE);
- mSwitch.setVisibility(View.GONE);
- LogUtil.e(LogUtil.BUGLE_TAG, "Unexpected keytype: " + keyType);
- break;
- }
- }
-
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- mListener.onValueChanged(mKey, mKeyType, String.valueOf(isChecked));
- }
-
- @Override
- public void onClick(View v) {
- if (MmsConfig.KEY_TYPE_BOOL.equals(mKeyType)) {
- return;
- }
- final Context context = getContext();
- mEditText = new EditText(context);
- mEditText.setText(mTextValue.getText());
- mEditText.setFocusable(true);
- if (MmsConfig.KEY_TYPE_INT.equals(mKeyType)) {
- mEditText.setInputType(InputType.TYPE_CLASS_PHONE);
- } else {
- mEditText.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
- }
- final AlertDialog dialog = new AlertDialog.Builder(context)
- .setTitle(mKey)
- .setView(mEditText)
- .setPositiveButton(android.R.string.ok, this)
- .setNegativeButton(android.R.string.cancel, null)
- .create();
- dialog.setOnShowListener(new OnShowListener() {
- @Override
- public void onShow(DialogInterface dialog) {
- mEditText.requestFocus();
- mEditText.selectAll();
- ((InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE))
- .toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0);
- }
- });
- dialog.show();
- }
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mListener.onValueChanged(mKey, mKeyType, mEditText.getText().toString());
- }
-}
diff --git a/src/com/android/messaging/ui/debug/DebugSmsMmsFromDumpFileDialogFragment.java b/src/com/android/messaging/ui/debug/DebugSmsMmsFromDumpFileDialogFragment.java
deleted file mode 100644
index 1aa8be3..0000000
--- a/src/com/android/messaging/ui/debug/DebugSmsMmsFromDumpFileDialogFragment.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * 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.debug;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Environment;
-import android.telephony.SmsMessage;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.action.ReceiveMmsMessageAction;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.receiver.SmsReceiver;
-import com.android.messaging.sms.MmsUtils;
-import com.android.messaging.util.DebugUtils;
-import com.android.messaging.util.LogUtil;
-
-/**
- * Class that displays UI for choosing SMS/MMS dump files for debugging
- */
-public class DebugSmsMmsFromDumpFileDialogFragment extends DialogFragment {
- public static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
- public static final String KEY_DUMP_FILES = "dump_files";
- public static final String KEY_ACTION = "action";
-
- public static final String ACTION_LOAD = "load";
- public static final String ACTION_EMAIL = "email";
-
- private String[] mDumpFiles;
- private String mAction;
-
- public static DebugSmsMmsFromDumpFileDialogFragment newInstance(final String[] dumpFiles,
- final String action) {
- final DebugSmsMmsFromDumpFileDialogFragment frag =
- new DebugSmsMmsFromDumpFileDialogFragment();
- final Bundle args = new Bundle();
- args.putSerializable(KEY_DUMP_FILES, dumpFiles);
- args.putString(KEY_ACTION, action);
- frag.setArguments(args);
- return frag;
- }
-
- @Override
- public Dialog onCreateDialog(final Bundle savedInstanceState) {
- final Bundle args = getArguments();
- mDumpFiles = (String[]) args.getSerializable(KEY_DUMP_FILES);
- mAction = args.getString(KEY_ACTION);
-
- final LayoutInflater inflater = getActivity().getLayoutInflater();
- final View layout = inflater.inflate(
- R.layout.debug_sms_mms_from_dump_file_dialog, null/*root*/);
- final ListView list = (ListView) layout.findViewById(R.id.dump_file_list);
- list.setAdapter(new DumpFileListAdapter(getActivity(), mDumpFiles));
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- final Resources resources = getResources();
- if (ACTION_LOAD.equals(mAction)) {
- builder.setTitle(resources.getString(
- R.string.load_sms_mms_from_dump_file_dialog_title));
- } else if (ACTION_EMAIL.equals(mAction)) {
- builder.setTitle(resources.getString(
- R.string.email_sms_mms_from_dump_file_dialog_title));
- }
- builder.setView(layout);
- return builder.create();
- }
-
- private class DumpFileListAdapter extends ArrayAdapter<String> {
- public DumpFileListAdapter(final Context context, final String[] dumpFiles) {
- super(context, R.layout.sms_mms_dump_file_list_item, dumpFiles);
- }
-
- @Override
- public View getView(final int position, final View view, final ViewGroup parent) {
- TextView actionItemView;
- if (view == null || !(view instanceof TextView)) {
- final LayoutInflater inflater = LayoutInflater.from(getContext());
- actionItemView = (TextView) inflater.inflate(
- R.layout.sms_mms_dump_file_list_item, parent, false);
- } else {
- actionItemView = (TextView) view;
- }
-
- final String file = getItem(position);
- actionItemView.setText(file);
- actionItemView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(final View view) {
- dismiss();
- if (ACTION_LOAD.equals(mAction)) {
- receiveFromDumpFile(file);
- } else if (ACTION_EMAIL.equals(mAction)) {
- emailDumpFile(file);
- }
- }
- });
- return actionItemView;
- }
- }
-
- /**
- * Load MMS/SMS from the dump file
- */
- private void receiveFromDumpFile(final String dumpFileName) {
- if (dumpFileName.startsWith(MmsUtils.SMS_DUMP_PREFIX)) {
- final SmsMessage[] messages = DebugUtils.retreiveSmsFromDumpFile(dumpFileName);
- if (messages != null) {
- SmsReceiver.deliverSmsMessages(getActivity(), ParticipantData.DEFAULT_SELF_SUB_ID,
- 0, messages);
- } else {
- LogUtil.e(LogUtil.BUGLE_TAG,
- "receiveFromDumpFile: invalid sms dump file " + dumpFileName);
- }
- } else if (dumpFileName.startsWith(MmsUtils.MMS_DUMP_PREFIX)) {
- final byte[] data = MmsUtils.createDebugNotificationInd(dumpFileName);
- if (data != null) {
- final ReceiveMmsMessageAction action = new ReceiveMmsMessageAction(
- ParticipantData.DEFAULT_SELF_SUB_ID, data);
- action.start();
- } else {
- LogUtil.e(LogUtil.BUGLE_TAG,
- "receiveFromDumpFile: invalid mms dump file " + dumpFileName);
- }
- } else {
- LogUtil.e(LogUtil.BUGLE_TAG,
- "receiveFromDumpFile: invalid dump file name " + dumpFileName);
- }
- }
-
- /**
- * Launch email app to send the dump file
- */
- private void emailDumpFile(final String file) {
- final Resources resources = getResources();
- final String fileLocation = "file://"
- + Environment.getExternalStorageDirectory() + "/" + file;
- final Intent sharingIntent = new Intent(Intent.ACTION_SEND);
- sharingIntent.setType(APPLICATION_OCTET_STREAM);
- sharingIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(fileLocation));
- sharingIntent.putExtra(Intent.EXTRA_SUBJECT,
- resources.getString(R.string.email_sms_mms_dump_file_subject));
- getActivity().startActivity(Intent.createChooser(sharingIntent,
- resources.getString(R.string.email_sms_mms_dump_file_chooser_title)));
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/AudioLevelSource.java b/src/com/android/messaging/ui/mediapicker/AudioLevelSource.java
deleted file mode 100644
index a211058..0000000
--- a/src/com/android/messaging/ui/mediapicker/AudioLevelSource.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.mediapicker;
-
-import com.google.common.base.Preconditions;
-
-import javax.annotation.concurrent.ThreadSafe;
-
-/**
- * Keeps track of the speech level as last observed by the recognition
- * engine as microphone data flows through it. Can be polled by the UI to
- * animate its views.
- */
-@ThreadSafe
-public class AudioLevelSource {
- private volatile int mSpeechLevel;
- private volatile Listener mListener;
-
- public static final int LEVEL_UNKNOWN = -1;
-
- public interface Listener {
- void onSpeechLevel(int speechLevel);
- }
-
- public void setSpeechLevel(int speechLevel) {
- Preconditions.checkArgument(speechLevel >= 0 && speechLevel <= 100 ||
- speechLevel == LEVEL_UNKNOWN);
- mSpeechLevel = speechLevel;
- maybeNotify();
- }
-
- public int getSpeechLevel() {
- return mSpeechLevel;
- }
-
- public void reset() {
- setSpeechLevel(LEVEL_UNKNOWN);
- }
-
- public boolean isValid() {
- return mSpeechLevel > 0;
- }
-
- private void maybeNotify() {
- final Listener l = mListener;
- if (l != null) {
- l.onSpeechLevel(mSpeechLevel);
- }
- }
-
- public synchronized void setListener(Listener listener) {
- mListener = listener;
- }
-
- public synchronized void clearListener(Listener listener) {
- if (mListener == listener) {
- mListener = null;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java b/src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java
deleted file mode 100644
index 5d79293..0000000
--- a/src/com/android/messaging/ui/mediapicker/AudioMediaChooser.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.Manifest;
-import android.content.pm.PackageManager;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.util.OsUtil;
-
-/**
- * Chooser which allows the user to record audio
- */
-class AudioMediaChooser extends MediaChooser implements
- AudioRecordView.HostInterface {
- private View mEnabledView;
- private View mMissingPermissionView;
-
- AudioMediaChooser(final MediaPicker mediaPicker) {
- super(mediaPicker);
- }
-
- @Override
- public int getSupportedMediaTypes() {
- return MediaPicker.MEDIA_TYPE_AUDIO;
- }
-
- @Override
- public int getIconResource() {
- return R.drawable.ic_audio_light;
- }
-
- @Override
- public int getIconDescriptionResource() {
- return R.string.mediapicker_audioChooserDescription;
- }
-
- @Override
- public void onAudioRecorded(final MessagePartData item) {
- mMediaPicker.dispatchItemsSelected(item, true);
- }
-
- @Override
- public void setThemeColor(final int color) {
- if (mView != null) {
- ((AudioRecordView) mView).setThemeColor(color);
- }
- }
-
- @Override
- protected View createView(final ViewGroup container) {
- final LayoutInflater inflater = getLayoutInflater();
- final AudioRecordView view = (AudioRecordView) inflater.inflate(
- R.layout.mediapicker_audio_chooser,
- container /* root */,
- false /* attachToRoot */);
- view.setHostInterface(this);
- view.setThemeColor(mMediaPicker.getConversationThemeColor());
- mEnabledView = view.findViewById(R.id.mediapicker_enabled);
- mMissingPermissionView = view.findViewById(R.id.missing_permission_view);
- return view;
- }
-
- @Override
- int getActionBarTitleResId() {
- return R.string.mediapicker_audio_title;
- }
-
- @Override
- public boolean isHandlingTouch() {
- // Whenever the user is in the process of recording audio, we want to allow the user
- // to move the finger within the panel without interpreting that as dragging the media
- // picker panel.
- return ((AudioRecordView) mView).shouldHandleTouch();
- }
-
- @Override
- public void stopTouchHandling() {
- ((AudioRecordView) mView).stopTouchHandling();
- }
-
- @Override
- public void onPause() {
- super.onPause();
- if (mView != null) {
- ((AudioRecordView) mView).onPause();
- }
- }
-
- @Override
- protected void setSelected(final boolean selected) {
- super.setSelected(selected);
- if (selected && !OsUtil.hasRecordAudioPermission()) {
- requestRecordAudioPermission();
- }
- }
-
- private void requestRecordAudioPermission() {
- mMediaPicker.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO },
- MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE);
- }
-
- @Override
- protected void onRequestPermissionsResult(
- final int requestCode, final String permissions[], final int[] grantResults) {
- if (requestCode == MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE) {
- final boolean permissionGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
- mEnabledView.setVisibility(permissionGranted ? View.VISIBLE : View.GONE);
- mMissingPermissionView.setVisibility(permissionGranted ? View.GONE : View.VISIBLE);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/AudioRecordView.java b/src/com/android/messaging/ui/mediapicker/AudioRecordView.java
deleted file mode 100644
index fba493f..0000000
--- a/src/com/android/messaging/ui/mediapicker/AudioRecordView.java
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.media.MediaRecorder;
-import android.net.Uri;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
-import com.android.messaging.datamodel.data.MediaPickerMessagePartData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.MediaUtil;
-import com.android.messaging.util.MediaUtil.OnCompletionListener;
-import com.android.messaging.util.SafeAsyncTask;
-import com.android.messaging.util.ThreadUtil;
-import com.android.messaging.util.UiUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-/**
- * Hosts an audio recorder with tap and hold to record functionality.
- */
-public class AudioRecordView extends FrameLayout implements
- MediaRecorder.OnErrorListener,
- MediaRecorder.OnInfoListener {
- /**
- * An interface that communicates with the hosted AudioRecordView.
- */
- public interface HostInterface extends DraftMessageSubscriptionDataProvider {
- void onAudioRecorded(final MessagePartData item);
- }
-
- /** The initial state, the user may press and hold to start recording */
- private static final int MODE_IDLE = 1;
-
- /** The user has pressed the record button and we are playing the sound indicating the
- * start of recording session. Don't record yet since we don't want the beeping sound
- * to get into the recording. */
- private static final int MODE_STARTING = 2;
-
- /** When the user is actively recording */
- private static final int MODE_RECORDING = 3;
-
- /** When the user has finished recording, we need to record for some additional time. */
- private static final int MODE_STOPPING = 4;
-
- // Bug: 16020175: The framework's MediaRecorder would cut off the ending portion of the
- // recorded audio by about half a second. To mitigate this issue, we continue the recording
- // for some extra time before stopping it.
- private static final int AUDIO_RECORD_ENDING_BUFFER_MILLIS = 500;
-
- /**
- * The minimum duration of any recording. Below this threshold, it will be treated as if the
- * user clicked the record button and inform the user to tap and hold to record.
- */
- private static final int AUDIO_RECORD_MINIMUM_DURATION_MILLIS = 300;
-
- // For accessibility, the touchable record button is bigger than the record button visual.
- private ImageView mRecordButtonVisual;
- private View mRecordButton;
- private SoundLevels mSoundLevels;
- private TextView mHintTextView;
- private PausableChronometer mTimerTextView;
- private LevelTrackingMediaRecorder mMediaRecorder;
- private long mAudioRecordStartTimeMillis;
-
- private int mCurrentMode = MODE_IDLE;
- private HostInterface mHostInterface;
- private int mThemeColor;
-
- public AudioRecordView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mMediaRecorder = new LevelTrackingMediaRecorder();
- }
-
- public void setHostInterface(final HostInterface hostInterface) {
- mHostInterface = hostInterface;
- }
-
- @VisibleForTesting
- public void testSetMediaRecorder(final LevelTrackingMediaRecorder recorder) {
- mMediaRecorder = recorder;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mSoundLevels = (SoundLevels) findViewById(R.id.sound_levels);
- mRecordButtonVisual = (ImageView) findViewById(R.id.record_button_visual);
- mRecordButton = findViewById(R.id.record_button);
- mHintTextView = (TextView) findViewById(R.id.hint_text);
- mTimerTextView = (PausableChronometer) findViewById(R.id.timer_text);
- mSoundLevels.setLevelSource(mMediaRecorder.getLevelSource());
- mRecordButton.setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(final View v, final MotionEvent event) {
- final int action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- onRecordButtonTouchDown();
-
- // Don't let the record button handle the down event to let it fall through
- // so that we can handle it for the entire panel in onTouchEvent(). This is
- // done so that: 1) the user taps on the record button to start recording
- // 2) the entire panel owns the touch event so we'd keep recording even
- // if the user moves outside the button region.
- return false;
- }
- return false;
- }
- });
- }
-
- @Override
- public boolean onTouchEvent(final MotionEvent event) {
- final int action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- return shouldHandleTouch();
-
- case MotionEvent.ACTION_MOVE:
- return true;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- return onRecordButtonTouchUp();
- }
- return super.onTouchEvent(event);
- }
-
- public void onPause() {
- // The conversation draft cannot take any updates when it's paused. Therefore, forcibly
- // stop recording on pause.
- stopRecording();
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- stopRecording();
- }
-
- private boolean isRecording() {
- return mMediaRecorder.isRecording() && mCurrentMode == MODE_RECORDING;
- }
-
- public boolean shouldHandleTouch() {
- return mCurrentMode != MODE_IDLE;
- }
-
- public void stopTouchHandling() {
- setMode(MODE_IDLE);
- stopRecording();
- }
-
- private void setMode(final int mode) {
- if (mCurrentMode != mode) {
- mCurrentMode = mode;
- updateVisualState();
- }
- }
-
- private void updateVisualState() {
- switch (mCurrentMode) {
- case MODE_IDLE:
- mHintTextView.setVisibility(VISIBLE);
- mHintTextView.setTypeface(null, Typeface.NORMAL);
- mTimerTextView.setVisibility(GONE);
- mSoundLevels.setEnabled(false);
- mTimerTextView.stop();
- break;
-
- case MODE_RECORDING:
- case MODE_STOPPING:
- mHintTextView.setVisibility(GONE);
- mTimerTextView.setVisibility(VISIBLE);
- mSoundLevels.setEnabled(true);
- mTimerTextView.restart();
- break;
-
- case MODE_STARTING:
- break; // No-Op.
-
- default:
- Assert.fail("invalid mode for AudioRecordView!");
- break;
- }
- updateRecordButtonAppearance();
- }
-
- public void setThemeColor(final int color) {
- mThemeColor = color;
- updateRecordButtonAppearance();
- }
-
- private void updateRecordButtonAppearance() {
- final Drawable foregroundDrawable = getResources().getDrawable(R.drawable.ic_mp_audio_mic);
- final GradientDrawable backgroundDrawable = ((GradientDrawable) getResources()
- .getDrawable(R.drawable.audio_record_control_button_background));
- if (isRecording()) {
- foregroundDrawable.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP);
- backgroundDrawable.setColor(mThemeColor);
- } else {
- foregroundDrawable.setColorFilter(mThemeColor, PorterDuff.Mode.SRC_ATOP);
- backgroundDrawable.setColor(Color.WHITE);
- }
- mRecordButtonVisual.setImageDrawable(foregroundDrawable);
- mRecordButtonVisual.setBackground(backgroundDrawable);
- }
-
- @VisibleForTesting
- boolean onRecordButtonTouchDown() {
- if (!mMediaRecorder.isRecording() && mCurrentMode == MODE_IDLE) {
- setMode(MODE_STARTING);
- playAudioStartSound(new OnCompletionListener() {
- @Override
- public void onCompletion() {
- // Double-check the current mode before recording since the user may have
- // lifted finger from the button before the beeping sound is played through.
- final int maxSize = MmsConfig.get(mHostInterface.getConversationSelfSubId())
- .getMaxMessageSize();
- if (mCurrentMode == MODE_STARTING &&
- mMediaRecorder.startRecording(AudioRecordView.this,
- AudioRecordView.this, maxSize)) {
- setMode(MODE_RECORDING);
- }
- }
- });
- mAudioRecordStartTimeMillis = System.currentTimeMillis();
- return true;
- }
- return false;
- }
-
- @VisibleForTesting
- boolean onRecordButtonTouchUp() {
- if (System.currentTimeMillis() - mAudioRecordStartTimeMillis <
- AUDIO_RECORD_MINIMUM_DURATION_MILLIS) {
- // The recording is too short, bolden the hint text to instruct the user to
- // "tap+hold" to record audio.
- final Uri outputUri = stopRecording();
- if (outputUri != null) {
- SafeAsyncTask.executeOnThreadPool(new Runnable() {
- @Override
- public void run() {
- Factory.get().getApplicationContext().getContentResolver().delete(
- outputUri, null, null);
- }
- });
- }
- setMode(MODE_IDLE);
- mHintTextView.setTypeface(null, Typeface.BOLD);
- } else if (isRecording()) {
- // Record for some extra time to ensure the ending part is saved.
- setMode(MODE_STOPPING);
- ThreadUtil.getMainThreadHandler().postDelayed(new Runnable() {
- @Override
- public void run() {
- onFinishedRecording();
- }
- }, AUDIO_RECORD_ENDING_BUFFER_MILLIS);
- } else {
- setMode(MODE_IDLE);
- }
- return true;
- }
-
- private Uri stopRecording() {
- if (mMediaRecorder.isRecording()) {
- return mMediaRecorder.stopRecording();
- }
- return null;
- }
-
- @Override // From MediaRecorder.OnInfoListener
- public void onInfo(final MediaRecorder mr, final int what, final int extra) {
- if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
- // Max size reached. Finish recording immediately.
- LogUtil.i(LogUtil.BUGLE_TAG, "Max size reached while recording audio");
- onFinishedRecording();
- } else {
- // These are unknown errors.
- onErrorWhileRecording(what, extra);
- }
- }
-
- @Override // From MediaRecorder.OnErrorListener
- public void onError(final MediaRecorder mr, final int what, final int extra) {
- onErrorWhileRecording(what, extra);
- }
-
- private void onErrorWhileRecording(final int what, final int extra) {
- LogUtil.e(LogUtil.BUGLE_TAG, "Error occurred during audio recording what=" + what +
- ", extra=" + extra);
- UiUtils.showToastAtBottom(R.string.audio_recording_error);
- setMode(MODE_IDLE);
- stopRecording();
- }
-
- private void onFinishedRecording() {
- final Uri outputUri = stopRecording();
- if (outputUri != null) {
- final Rect startRect = new Rect();
- mRecordButtonVisual.getGlobalVisibleRect(startRect);
- final MediaPickerMessagePartData audioItem =
- new MediaPickerMessagePartData(startRect,
- ContentType.AUDIO_3GPP, outputUri, 0, 0);
- mHostInterface.onAudioRecorded(audioItem);
- }
- playAudioEndSound();
- setMode(MODE_IDLE);
- }
-
- private void playAudioStartSound(final OnCompletionListener completionListener) {
- MediaUtil.get().playSound(getContext(), R.raw.audio_initiate, completionListener);
- }
-
- private void playAudioEndSound() {
- MediaUtil.get().playSound(getContext(), R.raw.audio_end, null);
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/CameraManager.java b/src/com/android/messaging/ui/mediapicker/CameraManager.java
deleted file mode 100644
index 166ebd7..0000000
--- a/src/com/android/messaging/ui/mediapicker/CameraManager.java
+++ /dev/null
@@ -1,1200 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.Manifest;
-import android.app.Activity;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.hardware.Camera;
-import android.hardware.Camera.CameraInfo;
-import android.media.MediaRecorder;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Looper;
-import android.support.annotation.NonNull;
-import android.text.TextUtils;
-import android.util.DisplayMetrics;
-import android.view.MotionEvent;
-import android.view.OrientationEventListener;
-import android.view.Surface;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.data.ParticipantData;
-import com.android.messaging.datamodel.media.ImageRequest;
-import com.android.messaging.sms.MmsConfig;
-import com.android.messaging.ui.mediapicker.camerafocus.FocusOverlayManager;
-import com.android.messaging.ui.mediapicker.camerafocus.RenderOverlay;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.BugleGservices;
-import com.android.messaging.util.BugleGservicesKeys;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.UiUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
-/**
- * Class which manages interactions with the camera, but does not do any UI. This class is
- * designed to be a singleton to ensure there is one component managing the camera and releasing
- * the native resources.
- * In order to acquire a camera, a caller must:
- * <ul>
- * <li>Call selectCamera to select front or back camera
- * <li>Call setSurface to control where the preview is shown
- * <li>Call openCamera to request the camera start preview
- * </ul>
- * Callers should call onPause and onResume to ensure that the camera is release while the activity
- * is not active.
- * This class is not thread safe. It should only be called from one thread (the UI thread or test
- * thread)
- */
-class CameraManager implements FocusOverlayManager.Listener {
- /**
- * Wrapper around the framework camera API to allow mocking different hardware scenarios while
- * unit testing
- */
- interface CameraWrapper {
- int getNumberOfCameras();
- void getCameraInfo(int index, CameraInfo cameraInfo);
- Camera open(int cameraId);
- /** Add a wrapper for release because a final method cannot be mocked */
- void release(Camera camera);
- }
-
- /**
- * Callbacks for the camera manager listener
- */
- interface CameraManagerListener {
- void onCameraError(int errorCode, Exception e);
- void onCameraChanged();
- }
-
- /**
- * Callback when taking image or video
- */
- interface MediaCallback {
- static final int MEDIA_CAMERA_CHANGED = 1;
- static final int MEDIA_NO_DATA = 2;
-
- void onMediaReady(Uri uriToMedia, String contentType, int width, int height);
- void onMediaFailed(Exception exception);
- void onMediaInfo(int what);
- }
-
- // Error codes
- static final int ERROR_OPENING_CAMERA = 1;
- static final int ERROR_SHOWING_PREVIEW = 2;
- static final int ERROR_INITIALIZING_VIDEO = 3;
- static final int ERROR_STORAGE_FAILURE = 4;
- static final int ERROR_RECORDING_VIDEO = 5;
- static final int ERROR_HARDWARE_ACCELERATION_DISABLED = 6;
- static final int ERROR_TAKING_PICTURE = 7;
-
- private static final String TAG = LogUtil.BUGLE_TAG;
- private static final int NO_CAMERA_SELECTED = -1;
-
- private static CameraManager sInstance;
-
- /** Default camera wrapper which directs calls to the framework APIs */
- private static CameraWrapper sCameraWrapper = new CameraWrapper() {
- @Override
- public int getNumberOfCameras() {
- return Camera.getNumberOfCameras();
- }
-
- @Override
- public void getCameraInfo(final int index, final CameraInfo cameraInfo) {
- Camera.getCameraInfo(index, cameraInfo);
- }
-
- @Override
- public Camera open(final int cameraId) {
- return Camera.open(cameraId);
- }
-
- @Override
- public void release(final Camera camera) {
- camera.release();
- }
- };
-
- /** The CameraInfo for the currently selected camera */
- private final CameraInfo mCameraInfo;
-
- /**
- * The index of the selected camera or NO_CAMERA_SELECTED if a camera hasn't been selected yet
- */
- private int mCameraIndex;
-
- /** True if the device has front and back cameras */
- private final boolean mHasFrontAndBackCamera;
-
- /** True if the camera should be open (may not yet be actually open) */
- private boolean mOpenRequested;
-
- /** True if the camera is requested to be in video mode */
- private boolean mVideoModeRequested;
-
- /** The media recorder for video mode */
- private MmsVideoRecorder mMediaRecorder;
-
- /** Callback to call with video recording updates */
- private MediaCallback mVideoCallback;
-
- /** The preview view to show the preview on */
- private CameraPreview mCameraPreview;
-
- /** The helper classs to handle orientation changes */
- private OrientationHandler mOrientationHandler;
-
- /** Tracks whether the preview has hardware acceleration */
- private boolean mIsHardwareAccelerationSupported;
-
- /**
- * The task for opening the camera, so it doesn't block the UI thread
- * Using AsyncTask rather than SafeAsyncTask because the tasks need to be serialized, but don't
- * need to be on the UI thread
- * TODO: If we have other AyncTasks (not SafeAsyncTasks) this may contend and we may
- * need to create a dedicated thread, or synchronize the threads in the thread pool
- */
- private AsyncTask<Integer, Void, Camera> mOpenCameraTask;
-
- /**
- * The camera index that is queued to be opened, but not completed yet, or NO_CAMERA_SELECTED if
- * no open task is pending
- */
- private int mPendingOpenCameraIndex = NO_CAMERA_SELECTED;
-
- /** The instance of the currently opened camera */
- private Camera mCamera;
-
- /** The rotation of the screen relative to the camera's natural orientation */
- private int mRotation;
-
- /** The callback to notify when errors or other events occur */
- private CameraManagerListener mListener;
-
- /** True if the camera is currently in the process of taking an image */
- private boolean mTakingPicture;
-
- /** Provides subscription-related data to access per-subscription configurations. */
- private DraftMessageSubscriptionDataProvider mSubscriptionDataProvider;
-
- /** Manages auto focus visual and behavior */
- private final FocusOverlayManager mFocusOverlayManager;
-
- private CameraManager() {
- mCameraInfo = new CameraInfo();
- mCameraIndex = NO_CAMERA_SELECTED;
-
- // Check to see if a front and back camera exist
- boolean hasFrontCamera = false;
- boolean hasBackCamera = false;
- final CameraInfo cameraInfo = new CameraInfo();
- final int cameraCount = sCameraWrapper.getNumberOfCameras();
- try {
- for (int i = 0; i < cameraCount; i++) {
- sCameraWrapper.getCameraInfo(i, cameraInfo);
- if (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT) {
- hasFrontCamera = true;
- } else if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
- hasBackCamera = true;
- }
- if (hasFrontCamera && hasBackCamera) {
- break;
- }
- }
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "Unable to load camera info", e);
- }
- mHasFrontAndBackCamera = hasFrontCamera && hasBackCamera;
- mFocusOverlayManager = new FocusOverlayManager(this, Looper.getMainLooper());
-
- // Assume the best until we are proven otherwise
- mIsHardwareAccelerationSupported = true;
- }
-
- /** Gets the singleton instance */
- static CameraManager get() {
- if (sInstance == null) {
- sInstance = new CameraManager();
- }
- return sInstance;
- }
-
- /** Allows tests to inject a custom camera wrapper */
- @VisibleForTesting
- static void setCameraWrapper(final CameraWrapper cameraWrapper) {
- sCameraWrapper = cameraWrapper;
- sInstance = null;
- }
-
- /**
- * Sets the surface to use to display the preview
- * This must only be called AFTER the CameraPreview has a texture ready
- * @param preview The preview surface view
- */
- void setSurface(final CameraPreview preview) {
- if (preview == mCameraPreview) {
- return;
- }
-
- if (preview != null) {
- Assert.isTrue(preview.isValid());
- preview.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(final View view, final MotionEvent motionEvent) {
- if ((motionEvent.getActionMasked() & MotionEvent.ACTION_UP) ==
- MotionEvent.ACTION_UP) {
- mFocusOverlayManager.setPreviewSize(view.getWidth(), view.getHeight());
- mFocusOverlayManager.onSingleTapUp(
- (int) motionEvent.getX() + view.getLeft(),
- (int) motionEvent.getY() + view.getTop());
- }
- return true;
- }
- });
- }
- mCameraPreview = preview;
- tryShowPreview();
- }
-
- void setRenderOverlay(final RenderOverlay renderOverlay) {
- mFocusOverlayManager.setFocusRenderer(renderOverlay != null ?
- renderOverlay.getPieRenderer() : null);
- }
-
- /** Convenience function to swap between front and back facing cameras */
- void swapCamera() {
- Assert.isTrue(mCameraIndex >= 0);
- selectCamera(mCameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT ?
- CameraInfo.CAMERA_FACING_BACK :
- CameraInfo.CAMERA_FACING_FRONT);
- }
-
- /**
- * Selects the first camera facing the desired direction, or the first camera if there is no
- * camera in the desired direction
- * @param desiredFacing One of the CameraInfo.CAMERA_FACING_* constants
- * @return True if a camera was selected, or false if selecting a camera failed
- */
- boolean selectCamera(final int desiredFacing) {
- try {
- // We already selected a camera facing that direction
- if (mCameraIndex >= 0 && mCameraInfo.facing == desiredFacing) {
- return true;
- }
-
- final int cameraCount = sCameraWrapper.getNumberOfCameras();
- Assert.isTrue(cameraCount > 0);
-
- mCameraIndex = NO_CAMERA_SELECTED;
- setCamera(null);
- final CameraInfo cameraInfo = new CameraInfo();
- for (int i = 0; i < cameraCount; i++) {
- sCameraWrapper.getCameraInfo(i, cameraInfo);
- if (cameraInfo.facing == desiredFacing) {
- mCameraIndex = i;
- sCameraWrapper.getCameraInfo(i, mCameraInfo);
- break;
- }
- }
-
- // There's no camera in the desired facing direction, just select the first camera
- // regardless of direction
- if (mCameraIndex < 0) {
- mCameraIndex = 0;
- sCameraWrapper.getCameraInfo(0, mCameraInfo);
- }
-
- if (mOpenRequested) {
- // The camera is open, so reopen with the newly selected camera
- openCamera();
- }
- return true;
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.selectCamera", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- return false;
- }
- }
-
- int getCameraIndex() {
- return mCameraIndex;
- }
-
- void selectCameraByIndex(final int cameraIndex) {
- if (mCameraIndex == cameraIndex) {
- return;
- }
-
- try {
- mCameraIndex = cameraIndex;
- sCameraWrapper.getCameraInfo(mCameraIndex, mCameraInfo);
- if (mOpenRequested) {
- openCamera();
- }
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.selectCameraByIndex", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- }
- }
-
- @VisibleForTesting
- CameraInfo getCameraInfo() {
- if (mCameraIndex == NO_CAMERA_SELECTED) {
- return null;
- }
- return mCameraInfo;
- }
-
- /** @return True if this device has camera capabilities */
- boolean hasAnyCamera() {
- return sCameraWrapper.getNumberOfCameras() > 0;
- }
-
- /** @return True if the device has both a front and back camera */
- boolean hasFrontAndBackCamera() {
- return mHasFrontAndBackCamera;
- }
-
- /**
- * Opens the camera on a separate thread and initiates the preview if one is available
- */
- void openCamera() {
- if (mCameraIndex == NO_CAMERA_SELECTED) {
- // Ensure a selected camera if none is currently selected. This may happen if the
- // camera chooser is not the default media chooser.
- selectCamera(CameraInfo.CAMERA_FACING_BACK);
- }
- mOpenRequested = true;
- // We're already opening the camera or already have the camera handle, nothing more to do
- if (mPendingOpenCameraIndex == mCameraIndex || mCamera != null) {
- return;
- }
-
- // True if the task to open the camera has to be delayed until the current one completes
- boolean delayTask = false;
-
- // Cancel any previous open camera tasks
- if (mOpenCameraTask != null) {
- mPendingOpenCameraIndex = NO_CAMERA_SELECTED;
- delayTask = true;
- }
-
- mPendingOpenCameraIndex = mCameraIndex;
- mOpenCameraTask = new AsyncTask<Integer, Void, Camera>() {
- private Exception mException;
-
- @Override
- protected Camera doInBackground(final Integer... params) {
- try {
- final int cameraIndex = params[0];
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Opening camera " + mCameraIndex);
- }
- return sCameraWrapper.open(cameraIndex);
- } catch (final Exception e) {
- LogUtil.e(TAG, "Exception while opening camera", e);
- mException = e;
- return null;
- }
- }
-
- @Override
- protected void onPostExecute(final Camera camera) {
- // If we completed, but no longer want this camera, then release the camera
- if (mOpenCameraTask != this || !mOpenRequested) {
- releaseCamera(camera);
- cleanup();
- return;
- }
-
- cleanup();
-
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Opened camera " + mCameraIndex + " " + (camera != null));
- }
-
- setCamera(camera);
- if (camera == null) {
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, mException);
- }
- LogUtil.e(TAG, "Error opening camera");
- }
- }
-
- @Override
- protected void onCancelled() {
- super.onCancelled();
- cleanup();
- }
-
- private void cleanup() {
- mPendingOpenCameraIndex = NO_CAMERA_SELECTED;
- if (mOpenCameraTask != null && mOpenCameraTask.getStatus() == Status.PENDING) {
- // If there's another task waiting on this one to complete, start it now
- mOpenCameraTask.execute(mCameraIndex);
- } else {
- mOpenCameraTask = null;
- }
-
- }
- };
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Start opening camera " + mCameraIndex);
- }
-
- if (!delayTask) {
- mOpenCameraTask.execute(mCameraIndex);
- }
- }
-
- boolean isVideoMode() {
- return mVideoModeRequested;
- }
-
- boolean isRecording() {
- return mVideoModeRequested && mVideoCallback != null;
- }
-
- void setVideoMode(final boolean videoMode) {
- if (mVideoModeRequested == videoMode) {
- return;
- }
- mVideoModeRequested = videoMode;
- tryInitOrCleanupVideoMode();
- }
-
- /** Closes the camera releasing the resources it uses */
- void closeCamera() {
- mOpenRequested = false;
- setCamera(null);
- }
-
- /** Temporarily closes the camera if it is open */
- void onPause() {
- setCamera(null);
- }
-
- /** Reopens the camera if it was opened when onPause was called */
- void onResume() {
- if (mOpenRequested) {
- openCamera();
- }
- }
-
- /**
- * Sets the listener which will be notified of errors or other events in the camera
- * @param listener The listener to notify
- */
- void setListener(final CameraManagerListener listener) {
- Assert.isMainThread();
- mListener = listener;
- if (!mIsHardwareAccelerationSupported && mListener != null) {
- mListener.onCameraError(ERROR_HARDWARE_ACCELERATION_DISABLED, null);
- }
- }
-
- void setSubscriptionDataProvider(final DraftMessageSubscriptionDataProvider provider) {
- mSubscriptionDataProvider = provider;
- }
-
- void takePicture(final float heightPercent, @NonNull final MediaCallback callback) {
- Assert.isTrue(!mVideoModeRequested);
- Assert.isTrue(!mTakingPicture);
- Assert.notNull(callback);
- if (mCamera == null) {
- // The caller should have checked isCameraAvailable first, but just in case, protect
- // against a null camera by notifying the callback that taking the picture didn't work
- callback.onMediaFailed(null);
- return;
- }
- final Camera.PictureCallback jpegCallback = new Camera.PictureCallback() {
- @Override
- public void onPictureTaken(final byte[] bytes, final Camera camera) {
- mTakingPicture = false;
- if (mCamera != camera) {
- // This may happen if the camera was changed between front/back while the
- // picture is being taken.
- callback.onMediaInfo(MediaCallback.MEDIA_CAMERA_CHANGED);
- return;
- }
-
- if (bytes == null) {
- callback.onMediaInfo(MediaCallback.MEDIA_NO_DATA);
- return;
- }
-
- final Camera.Size size = camera.getParameters().getPictureSize();
- int width;
- int height;
- if (mRotation == 90 || mRotation == 270) {
- width = size.height;
- height = size.width;
- } else {
- width = size.width;
- height = size.height;
- }
- new ImagePersistTask(
- width, height, heightPercent, bytes, mCameraPreview.getContext(), callback)
- .executeOnThreadPool();
- }
- };
-
- mTakingPicture = true;
- try {
- mCamera.takePicture(
- null /* shutter */,
- null /* raw */,
- null /* postView */,
- jpegCallback);
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.takePicture", e);
- mTakingPicture = false;
- if (mListener != null) {
- mListener.onCameraError(ERROR_TAKING_PICTURE, e);
- }
- }
- }
-
- void startVideo(final MediaCallback callback) {
- Assert.notNull(callback);
- Assert.isTrue(!isRecording());
- mVideoCallback = callback;
- tryStartVideoCapture();
- }
-
- /**
- * Asynchronously releases a camera
- * @param camera The camera to release
- */
- private void releaseCamera(final Camera camera) {
- if (camera == null) {
- return;
- }
-
- mFocusOverlayManager.onCameraReleased();
-
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(final Void... params) {
- if (LogUtil.isLoggable(TAG, LogUtil.VERBOSE)) {
- LogUtil.v(TAG, "Releasing camera " + mCameraIndex);
- }
- sCameraWrapper.release(camera);
- return null;
- }
- }.execute();
- }
-
- private void releaseMediaRecorder(final boolean cleanupFile) {
- if (mMediaRecorder == null) {
- return;
- }
- mVideoModeRequested = false;
-
- if (cleanupFile) {
- mMediaRecorder.cleanupTempFile();
- if (mVideoCallback != null) {
- final MediaCallback callback = mVideoCallback;
- mVideoCallback = null;
- // Notify the callback that we've stopped recording
- callback.onMediaReady(null /*uri*/, null /*contentType*/, 0 /*width*/,
- 0 /*height*/);
- }
- }
-
- mMediaRecorder.release();
- mMediaRecorder = null;
-
- if (mCamera != null) {
- try {
- mCamera.reconnect();
- } catch (final IOException e) {
- LogUtil.e(TAG, "IOException in CameraManager.releaseMediaRecorder", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.releaseMediaRecorder", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- }
- }
- restoreRequestedOrientation();
- }
-
- /** Updates the orientation of the camera to match the orientation of the device */
- private void updateCameraOrientation() {
- if (mCamera == null || mCameraPreview == null || mTakingPicture) {
- return;
- }
-
- final WindowManager windowManager =
- (WindowManager) mCameraPreview.getContext().getSystemService(
- Context.WINDOW_SERVICE);
-
- int degrees = 0;
- switch (windowManager.getDefaultDisplay().getRotation()) {
- case Surface.ROTATION_0: degrees = 0; break;
- case Surface.ROTATION_90: degrees = 90; break;
- case Surface.ROTATION_180: degrees = 180; break;
- case Surface.ROTATION_270: degrees = 270; break;
- }
-
- // The display orientation of the camera (this controls the preview image).
- int orientation;
-
- // The clockwise rotation angle relative to the orientation of the camera. This affects
- // pictures returned by the camera in Camera.PictureCallback.
- int rotation;
- if (mCameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
- orientation = (mCameraInfo.orientation + degrees) % 360;
- rotation = orientation;
- // compensate the mirror but only for orientation
- orientation = (360 - orientation) % 360;
- } else { // back-facing
- orientation = (mCameraInfo.orientation - degrees + 360) % 360;
- rotation = orientation;
- }
- mRotation = rotation;
- if (mMediaRecorder == null) {
- try {
- mCamera.setDisplayOrientation(orientation);
- final Camera.Parameters params = mCamera.getParameters();
- params.setRotation(rotation);
- mCamera.setParameters(params);
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.updateCameraOrientation", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_OPENING_CAMERA, e);
- }
- }
- }
- }
-
- /** Sets the current camera, releasing any previously opened camera */
- private void setCamera(final Camera camera) {
- if (mCamera == camera) {
- return;
- }
-
- releaseMediaRecorder(true /* cleanupFile */);
- releaseCamera(mCamera);
- mCamera = camera;
- tryShowPreview();
- if (mListener != null) {
- mListener.onCameraChanged();
- }
- }
-
- /** Shows the preview if the camera is open and the preview is loaded */
- private void tryShowPreview() {
- if (mCameraPreview == null || mCamera == null) {
- if (mOrientationHandler != null) {
- mOrientationHandler.disable();
- mOrientationHandler = null;
- }
- releaseMediaRecorder(true /* cleanupFile */);
- mFocusOverlayManager.onPreviewStopped();
- return;
- }
- try {
- mCamera.stopPreview();
- updateCameraOrientation();
-
- final Camera.Parameters params = mCamera.getParameters();
- final Camera.Size pictureSize = chooseBestPictureSize();
- final Camera.Size previewSize = chooseBestPreviewSize(pictureSize);
- params.setPreviewSize(previewSize.width, previewSize.height);
- params.setPictureSize(pictureSize.width, pictureSize.height);
- logCameraSize("Setting preview size: ", previewSize);
- logCameraSize("Setting picture size: ", pictureSize);
- mCameraPreview.setSize(previewSize, mCameraInfo.orientation);
- for (final String focusMode : params.getSupportedFocusModes()) {
- if (TextUtils.equals(focusMode, Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
- // Use continuous focus if available
- params.setFocusMode(focusMode);
- break;
- }
- }
-
- mCamera.setParameters(params);
- mCameraPreview.startPreview(mCamera);
- mCamera.startPreview();
- mCamera.setAutoFocusMoveCallback(new Camera.AutoFocusMoveCallback() {
- @Override
- public void onAutoFocusMoving(final boolean start, final Camera camera) {
- mFocusOverlayManager.onAutoFocusMoving(start);
- }
- });
- mFocusOverlayManager.setParameters(mCamera.getParameters());
- mFocusOverlayManager.setMirror(mCameraInfo.facing == CameraInfo.CAMERA_FACING_BACK);
- mFocusOverlayManager.onPreviewStarted();
- tryInitOrCleanupVideoMode();
- if (mOrientationHandler == null) {
- mOrientationHandler = new OrientationHandler(mCameraPreview.getContext());
- mOrientationHandler.enable();
- }
- } catch (final IOException e) {
- LogUtil.e(TAG, "IOException in CameraManager.tryShowPreview", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_SHOWING_PREVIEW, e);
- }
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.tryShowPreview", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_SHOWING_PREVIEW, e);
- }
- }
- }
-
- private void tryInitOrCleanupVideoMode() {
- if (!mVideoModeRequested || mCamera == null || mCameraPreview == null) {
- releaseMediaRecorder(true /* cleanupFile */);
- return;
- }
-
- if (mMediaRecorder != null) {
- return;
- }
-
- try {
- mCamera.unlock();
- final int maxMessageSize = getMmsConfig().getMaxMessageSize();
- mMediaRecorder = new MmsVideoRecorder(mCamera, mCameraIndex, mRotation, maxMessageSize);
- mMediaRecorder.prepare();
- } catch (final FileNotFoundException e) {
- LogUtil.e(TAG, "FileNotFoundException in CameraManager.tryInitOrCleanupVideoMode", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_STORAGE_FAILURE, e);
- }
- setVideoMode(false);
- return;
- } catch (final IOException e) {
- LogUtil.e(TAG, "IOException in CameraManager.tryInitOrCleanupVideoMode", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_INITIALIZING_VIDEO, e);
- }
- setVideoMode(false);
- return;
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.tryInitOrCleanupVideoMode", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_INITIALIZING_VIDEO, e);
- }
- setVideoMode(false);
- return;
- }
-
- tryStartVideoCapture();
- }
-
- private void tryStartVideoCapture() {
- if (mMediaRecorder == null || mVideoCallback == null) {
- return;
- }
-
- mMediaRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() {
- @Override
- public void onError(final MediaRecorder mediaRecorder, final int what,
- final int extra) {
- if (mListener != null) {
- mListener.onCameraError(ERROR_RECORDING_VIDEO, null);
- }
- restoreRequestedOrientation();
- }
- });
-
- mMediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener() {
- @Override
- public void onInfo(final MediaRecorder mediaRecorder, final int what, final int extra) {
- if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED ||
- what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
- stopVideo();
- }
- }
- });
-
- try {
- mMediaRecorder.start();
- final Activity activity = UiUtils.getActivity(mCameraPreview.getContext());
- activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- lockOrientation();
- } catch (final IllegalStateException e) {
- LogUtil.e(TAG, "IllegalStateException in CameraManager.tryStartVideoCapture", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_RECORDING_VIDEO, e);
- }
- setVideoMode(false);
- restoreRequestedOrientation();
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.tryStartVideoCapture", e);
- if (mListener != null) {
- mListener.onCameraError(ERROR_RECORDING_VIDEO, e);
- }
- setVideoMode(false);
- restoreRequestedOrientation();
- }
- }
-
- void stopVideo() {
- int width = ImageRequest.UNSPECIFIED_SIZE;
- int height = ImageRequest.UNSPECIFIED_SIZE;
- Uri uri = null;
- String contentType = null;
- try {
- final Activity activity = UiUtils.getActivity(mCameraPreview.getContext());
- activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- mMediaRecorder.stop();
- width = mMediaRecorder.getVideoWidth();
- height = mMediaRecorder.getVideoHeight();
- uri = mMediaRecorder.getVideoUri();
- contentType = mMediaRecorder.getContentType();
- } catch (final RuntimeException e) {
- // MediaRecorder.stop will throw a RuntimeException if the video was too short, let the
- // finally clause call the callback with null uri and handle cleanup
- LogUtil.e(TAG, "RuntimeException in CameraManager.stopVideo", e);
- } finally {
- final MediaCallback videoCallback = mVideoCallback;
- mVideoCallback = null;
- releaseMediaRecorder(false /* cleanupFile */);
- if (uri == null) {
- tryInitOrCleanupVideoMode();
- }
- videoCallback.onMediaReady(uri, contentType, width, height);
- }
- }
-
- boolean isCameraAvailable() {
- return mCamera != null && !mTakingPicture && mIsHardwareAccelerationSupported;
- }
-
- /**
- * External components call into this to report if hardware acceleration is supported. When
- * hardware acceleration isn't supported, we need to report an error through the listener
- * interface
- * @param isHardwareAccelerationSupported True if the preview is rendering in a hardware
- * accelerated view.
- */
- void reportHardwareAccelerationSupported(final boolean isHardwareAccelerationSupported) {
- Assert.isMainThread();
- if (mIsHardwareAccelerationSupported == isHardwareAccelerationSupported) {
- // If the value hasn't changed nothing more to do
- return;
- }
-
- mIsHardwareAccelerationSupported = isHardwareAccelerationSupported;
- if (!isHardwareAccelerationSupported) {
- LogUtil.e(TAG, "Software rendering - cannot open camera");
- if (mListener != null) {
- mListener.onCameraError(ERROR_HARDWARE_ACCELERATION_DISABLED, null);
- }
- }
- }
-
- /** Returns the scale factor to scale the width/height to max allowed in MmsConfig */
- private float getScaleFactorForMaxAllowedSize(final int width, final int height,
- final int maxWidth, final int maxHeight) {
- if (maxWidth <= 0 || maxHeight <= 0) {
- // MmsConfig initialization runs asynchronously on application startup, so there's a
- // chance (albeit a very slight one) that we don't have it yet.
- LogUtil.w(LogUtil.BUGLE_TAG, "Max image size not loaded in MmsConfig");
- return 1.0f;
- }
-
- if (width <= maxWidth && height <= maxHeight) {
- // Already meeting requirements.
- return 1.0f;
- }
-
- return Math.min(maxWidth * 1.0f / width, maxHeight * 1.0f / height);
- }
-
- private MmsConfig getMmsConfig() {
- final int subId = mSubscriptionDataProvider != null ?
- mSubscriptionDataProvider.getConversationSelfSubId() :
- ParticipantData.DEFAULT_SELF_SUB_ID;
- return MmsConfig.get(subId);
- }
-
- /**
- * Choose the best picture size by trying to find a size close to the MmsConfig's max size,
- * which is closest to the screen aspect ratio
- */
- private Camera.Size chooseBestPictureSize() {
- final Context context = mCameraPreview.getContext();
- final Resources resources = context.getResources();
- final DisplayMetrics displayMetrics = resources.getDisplayMetrics();
- final int displayOrientation = resources.getConfiguration().orientation;
- int cameraOrientation = mCameraInfo.orientation;
-
- int screenWidth;
- int screenHeight;
- if (displayOrientation == Configuration.ORIENTATION_LANDSCAPE) {
- // Rotate the camera orientation 90 degrees to compensate for the rotated display
- // metrics. Direction doesn't matter because we're just using it for width/height
- cameraOrientation += 90;
- }
-
- // Check the camera orientation relative to the display.
- // For 0, 180, 360, the screen width/height are the display width/height
- // For 90, 270, the screen width/height are inverted from the display
- if (cameraOrientation % 180 == 0) {
- screenWidth = displayMetrics.widthPixels;
- screenHeight = displayMetrics.heightPixels;
- } else {
- screenWidth = displayMetrics.heightPixels;
- screenHeight = displayMetrics.widthPixels;
- }
-
- final MmsConfig mmsConfig = getMmsConfig();
- final int maxWidth = mmsConfig.getMaxImageWidth();
- final int maxHeight = mmsConfig.getMaxImageHeight();
-
- // Constrain the size within the max width/height defined by MmsConfig.
- final float scaleFactor = getScaleFactorForMaxAllowedSize(screenWidth, screenHeight,
- maxWidth, maxHeight);
- screenWidth *= scaleFactor;
- screenHeight *= scaleFactor;
-
- final float aspectRatio = BugleGservices.get().getFloat(
- BugleGservicesKeys.CAMERA_ASPECT_RATIO,
- screenWidth / (float) screenHeight);
- final List<Camera.Size> sizes = new ArrayList<Camera.Size>(
- mCamera.getParameters().getSupportedPictureSizes());
- final int maxPixels = maxWidth * maxHeight;
-
- // Sort the sizes so the best size is first
- Collections.sort(sizes, new SizeComparator(maxWidth, maxHeight, aspectRatio, maxPixels));
-
- return sizes.get(0);
- }
-
- /**
- * Chose the best preview size based on the picture size. Try to find a size with the same
- * aspect ratio and size as the picture if possible
- */
- private Camera.Size chooseBestPreviewSize(final Camera.Size pictureSize) {
- final List<Camera.Size> sizes = new ArrayList<Camera.Size>(
- mCamera.getParameters().getSupportedPreviewSizes());
- final float aspectRatio = pictureSize.width / (float) pictureSize.height;
- final int capturePixels = pictureSize.width * pictureSize.height;
-
- // Sort the sizes so the best size is first
- Collections.sort(sizes, new SizeComparator(Integer.MAX_VALUE, Integer.MAX_VALUE,
- aspectRatio, capturePixels));
-
- return sizes.get(0);
- }
-
- private class OrientationHandler extends OrientationEventListener {
- OrientationHandler(final Context context) {
- super(context);
- }
-
- @Override
- public void onOrientationChanged(final int orientation) {
- updateCameraOrientation();
- }
- }
-
- private static class SizeComparator implements Comparator<Camera.Size> {
- private static final int PREFER_LEFT = -1;
- private static final int PREFER_RIGHT = 1;
-
- // The max width/height for the preferred size. Integer.MAX_VALUE if no size limit
- private final int mMaxWidth;
- private final int mMaxHeight;
-
- // The desired aspect ratio
- private final float mTargetAspectRatio;
-
- // The desired size (width x height) to try to match
- private final int mTargetPixels;
-
- public SizeComparator(final int maxWidth, final int maxHeight,
- final float targetAspectRatio, final int targetPixels) {
- mMaxWidth = maxWidth;
- mMaxHeight = maxHeight;
- mTargetAspectRatio = targetAspectRatio;
- mTargetPixels = targetPixels;
- }
-
- /**
- * Returns a negative value if left is a better choice than right, or a positive value if
- * right is a better choice is better than left. 0 if they are equal
- */
- @Override
- public int compare(final Camera.Size left, final Camera.Size right) {
- // If one size is less than the max size prefer it over the other
- if ((left.width <= mMaxWidth && left.height <= mMaxHeight) !=
- (right.width <= mMaxWidth && right.height <= mMaxHeight)) {
- return left.width <= mMaxWidth ? PREFER_LEFT : PREFER_RIGHT;
- }
-
- // If one is closer to the target aspect ratio, prefer it.
- final float leftAspectRatio = left.width / (float) left.height;
- final float rightAspectRatio = right.width / (float) right.height;
- final float leftAspectRatioDiff = Math.abs(leftAspectRatio - mTargetAspectRatio);
- final float rightAspectRatioDiff = Math.abs(rightAspectRatio - mTargetAspectRatio);
- if (leftAspectRatioDiff != rightAspectRatioDiff) {
- return (leftAspectRatioDiff - rightAspectRatioDiff) < 0 ?
- PREFER_LEFT : PREFER_RIGHT;
- }
-
- // At this point they have the same aspect ratio diff and are either both bigger
- // than the max size or both smaller than the max size, so prefer the one closest
- // to target size
- final int leftDiff = Math.abs((left.width * left.height) - mTargetPixels);
- final int rightDiff = Math.abs((right.width * right.height) - mTargetPixels);
- return leftDiff - rightDiff;
- }
- }
-
- @Override // From FocusOverlayManager.Listener
- public void autoFocus() {
- if (mCamera == null) {
- return;
- }
-
- try {
- mCamera.autoFocus(new Camera.AutoFocusCallback() {
- @Override
- public void onAutoFocus(final boolean success, final Camera camera) {
- mFocusOverlayManager.onAutoFocus(success, false /* shutterDown */);
- }
- });
- } catch (final RuntimeException e) {
- LogUtil.e(TAG, "RuntimeException in CameraManager.autoFocus", e);
- // If autofocus fails, the camera should have called the callback with success=false,
- // but some throw an exception here
- mFocusOverlayManager.onAutoFocus(false /*success*/, false /*shutterDown*/);
- }
- }
-
- @Override // From FocusOverlayManager.Listener
- public void cancelAutoFocus() {
- if (mCamera == null) {
- return;
- }
- try {
- mCamera.cancelAutoFocus();
- } catch (final RuntimeException e) {
- // Ignore
- LogUtil.e(TAG, "RuntimeException in CameraManager.cancelAutoFocus", e);
- }
- }
-
- @Override // From FocusOverlayManager.Listener
- public boolean capture() {
- return false;
- }
-
- @Override // From FocusOverlayManager.Listener
- public void setFocusParameters() {
- if (mCamera == null) {
- return;
- }
- try {
- final Camera.Parameters parameters = mCamera.getParameters();
- parameters.setFocusMode(mFocusOverlayManager.getFocusMode());
- if (parameters.getMaxNumFocusAreas() > 0) {
- // Don't set focus areas (even to null) if focus areas aren't supported, camera may
- // crash
- parameters.setFocusAreas(mFocusOverlayManager.getFocusAreas());
- }
- parameters.setMeteringAreas(mFocusOverlayManager.getMeteringAreas());
- mCamera.setParameters(parameters);
- } catch (final RuntimeException e) {
- // This occurs when the device is out of space or when the camera is locked
- LogUtil.e(TAG, "RuntimeException in CameraManager setFocusParameters");
- }
- }
-
- private void logCameraSize(final String prefix, final Camera.Size size) {
- // Log the camera size and aspect ratio for help when examining bug reports for camera
- // failures
- LogUtil.i(TAG, prefix + size.width + "x" + size.height +
- " (" + (size.width / (float) size.height) + ")");
- }
-
-
- private Integer mSavedOrientation = null;
-
- private void lockOrientation() {
- // when we start recording, lock our orientation
- final Activity a = UiUtils.getActivity(mCameraPreview.getContext());
- final WindowManager windowManager =
- (WindowManager) a.getSystemService(Context.WINDOW_SERVICE);
- final int rotation = windowManager.getDefaultDisplay().getRotation();
-
- mSavedOrientation = a.getRequestedOrientation();
- switch (rotation) {
- case Surface.ROTATION_0:
- a.setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- break;
- case Surface.ROTATION_90:
- a.setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
- break;
- case Surface.ROTATION_180:
- a.setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
- break;
- case Surface.ROTATION_270:
- a.setRequestedOrientation(
- ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
- break;
- }
-
- }
-
- private void restoreRequestedOrientation() {
- if (mSavedOrientation != null) {
- final Activity a = UiUtils.getActivity(mCameraPreview.getContext());
- if (a != null) {
- a.setRequestedOrientation(mSavedOrientation);
- }
- mSavedOrientation = null;
- }
- }
-
- static boolean hasCameraPermission() {
- return OsUtil.hasPermission(Manifest.permission.CAMERA);
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java b/src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java
deleted file mode 100644
index 2c7a7f2..0000000
--- a/src/com/android/messaging/ui/mediapicker/CameraMediaChooser.java
+++ /dev/null
@@ -1,481 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.graphics.Rect;
-import android.hardware.Camera;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
-import android.widget.Chronometer;
-import android.widget.ImageButton;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.MediaPickerMessagePartData;
-import com.android.messaging.ui.mediapicker.CameraManager.MediaCallback;
-import com.android.messaging.ui.mediapicker.camerafocus.RenderOverlay;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.UiUtils;
-
-/**
- * Chooser which allows the user to take pictures or video without leaving the current app/activity
- */
-class CameraMediaChooser extends MediaChooser implements
- CameraManager.CameraManagerListener {
- private CameraPreview.CameraPreviewHost mCameraPreviewHost;
- private ImageButton mFullScreenButton;
- private ImageButton mSwapCameraButton;
- private ImageButton mSwapModeButton;
- private ImageButton mCaptureButton;
- private ImageButton mCancelVideoButton;
- private Chronometer mVideoCounter;
- private boolean mVideoCancelled;
- private int mErrorToast;
- private View mEnabledView;
- private View mMissingPermissionView;
-
- CameraMediaChooser(final MediaPicker mediaPicker) {
- super(mediaPicker);
- }
-
- @Override
- public int getSupportedMediaTypes() {
- if (CameraManager.get().hasAnyCamera()) {
- return MediaPicker.MEDIA_TYPE_IMAGE | MediaPicker.MEDIA_TYPE_VIDEO;
- } else {
- return MediaPicker.MEDIA_TYPE_NONE;
- }
- }
-
- @Override
- public View destroyView() {
- CameraManager.get().closeCamera();
- CameraManager.get().setListener(null);
- CameraManager.get().setSubscriptionDataProvider(null);
- return super.destroyView();
- }
-
- @Override
- protected View createView(final ViewGroup container) {
- CameraManager.get().setListener(this);
- CameraManager.get().setSubscriptionDataProvider(this);
- CameraManager.get().setVideoMode(false);
- final LayoutInflater inflater = getLayoutInflater();
- final CameraMediaChooserView view = (CameraMediaChooserView) inflater.inflate(
- R.layout.mediapicker_camera_chooser,
- container /* root */,
- false /* attachToRoot */);
- mCameraPreviewHost = (CameraPreview.CameraPreviewHost) view.findViewById(
- R.id.camera_preview);
- mCameraPreviewHost.getView().setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(final View view, final MotionEvent motionEvent) {
- if (CameraManager.get().isVideoMode()) {
- // Prevent the swipe down in video mode because video is always captured in
- // full screen
- return true;
- }
-
- return false;
- }
- });
-
- final View shutterVisual = view.findViewById(R.id.camera_shutter_visual);
-
- mFullScreenButton = (ImageButton) view.findViewById(R.id.camera_fullScreen_button);
- mFullScreenButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View view) {
- mMediaPicker.setFullScreen(true);
- }
- });
-
- mSwapCameraButton = (ImageButton) view.findViewById(R.id.camera_swapCamera_button);
- mSwapCameraButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View view) {
- CameraManager.get().swapCamera();
- }
- });
-
- mCaptureButton = (ImageButton) view.findViewById(R.id.camera_capture_button);
- mCaptureButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View v) {
- final float heightPercent = Math.min(mMediaPicker.getViewPager().getHeight() /
- (float) mCameraPreviewHost.getView().getHeight(), 1);
-
- if (CameraManager.get().isRecording()) {
- CameraManager.get().stopVideo();
- } else {
- final CameraManager.MediaCallback callback = new CameraManager.MediaCallback() {
- @Override
- public void onMediaReady(
- final Uri uriToVideo, final String contentType,
- final int width, final int height) {
- mVideoCounter.stop();
- if (mVideoCancelled || uriToVideo == null) {
- mVideoCancelled = false;
- } else {
- final Rect startRect = new Rect();
- // It's possible to throw out the chooser while taking the
- // picture/video. In that case, still use the attachment, just
- // skip the startRect
- if (mView != null) {
- mView.getGlobalVisibleRect(startRect);
- }
- mMediaPicker.dispatchItemsSelected(
- new MediaPickerMessagePartData(startRect, contentType,
- uriToVideo, width, height),
- true /* dismissMediaPicker */);
- }
- updateViewState();
- }
-
- @Override
- public void onMediaFailed(final Exception exception) {
- UiUtils.showToastAtBottom(R.string.camera_media_failure);
- updateViewState();
- }
-
- @Override
- public void onMediaInfo(final int what) {
- if (what == MediaCallback.MEDIA_NO_DATA) {
- UiUtils.showToastAtBottom(R.string.camera_media_failure);
- }
- updateViewState();
- }
- };
- if (CameraManager.get().isVideoMode()) {
- CameraManager.get().startVideo(callback);
- mVideoCounter.setBase(SystemClock.elapsedRealtime());
- mVideoCounter.start();
- updateViewState();
- } else {
- showShutterEffect(shutterVisual);
- CameraManager.get().takePicture(heightPercent, callback);
- updateViewState();
- }
- }
- }
- });
-
- mSwapModeButton = (ImageButton) view.findViewById(R.id.camera_swap_mode_button);
- mSwapModeButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View view) {
- final boolean isSwitchingToVideo = !CameraManager.get().isVideoMode();
- if (isSwitchingToVideo && !OsUtil.hasRecordAudioPermission()) {
- requestRecordAudioPermission();
- } else {
- onSwapMode();
- }
- }
- });
-
- mCancelVideoButton = (ImageButton) view.findViewById(R.id.camera_cancel_button);
- mCancelVideoButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View view) {
- mVideoCancelled = true;
- CameraManager.get().stopVideo();
- mMediaPicker.dismiss(true);
- }
- });
-
- mVideoCounter = (Chronometer) view.findViewById(R.id.camera_video_counter);
-
- CameraManager.get().setRenderOverlay((RenderOverlay) view.findViewById(R.id.focus_visual));
-
- mEnabledView = view.findViewById(R.id.mediapicker_enabled);
- mMissingPermissionView = view.findViewById(R.id.missing_permission_view);
-
- // Must set mView before calling updateViewState because it operates on mView
- mView = view;
- updateViewState();
- updateForPermissionState(CameraManager.hasCameraPermission());
- return view;
- }
-
- @Override
- public int getIconResource() {
- return R.drawable.ic_camera_light;
- }
-
- @Override
- public int getIconDescriptionResource() {
- return R.string.mediapicker_cameraChooserDescription;
- }
-
- /**
- * Updates the view when entering or leaving full-screen camera mode
- * @param fullScreen
- */
- @Override
- void onFullScreenChanged(final boolean fullScreen) {
- super.onFullScreenChanged(fullScreen);
- if (!fullScreen && CameraManager.get().isVideoMode()) {
- CameraManager.get().setVideoMode(false);
- }
- updateViewState();
- }
-
- /**
- * Initializes the control to a default state when it is opened / closed
- * @param open True if the control is opened
- */
- @Override
- void onOpenedChanged(final boolean open) {
- super.onOpenedChanged(open);
- updateViewState();
- }
-
- @Override
- protected void setSelected(final boolean selected) {
- super.setSelected(selected);
- if (selected) {
- if (CameraManager.hasCameraPermission()) {
- // If an error occurred before the chooser was selected, show it now
- showErrorToastIfNeeded();
- } else {
- requestCameraPermission();
- }
- }
- }
-
- private void requestCameraPermission() {
- mMediaPicker.requestPermissions(new String[] { Manifest.permission.CAMERA },
- MediaPicker.CAMERA_PERMISSION_REQUEST_CODE);
- }
-
- private void requestRecordAudioPermission() {
- mMediaPicker.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO },
- MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE);
- }
-
- @Override
- protected void onRequestPermissionsResult(
- final int requestCode, final String permissions[], final int[] grantResults) {
- if (requestCode == MediaPicker.CAMERA_PERMISSION_REQUEST_CODE) {
- final boolean permissionGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
- updateForPermissionState(permissionGranted);
- if (permissionGranted) {
- mCameraPreviewHost.onCameraPermissionGranted();
- }
- } else if (requestCode == MediaPicker.RECORD_AUDIO_PERMISSION_REQUEST_CODE) {
- Assert.isFalse(CameraManager.get().isVideoMode());
- if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- // Switch to video mode
- onSwapMode();
- } else {
- // Stay in still-photo mode
- }
- }
- }
-
- private void updateForPermissionState(final boolean granted) {
- // onRequestPermissionsResult can sometimes get called before createView().
- if (mEnabledView == null) {
- return;
- }
-
- mEnabledView.setVisibility(granted ? View.VISIBLE : View.GONE);
- mMissingPermissionView.setVisibility(granted ? View.GONE : View.VISIBLE);
- }
-
- @Override
- public boolean canSwipeDown() {
- if (CameraManager.get().isVideoMode()) {
- return true;
- }
- return super.canSwipeDown();
- }
-
- /**
- * Handles an error from the camera manager by showing the appropriate error message to the user
- * @param errorCode One of the CameraManager.ERROR_* constants
- * @param e The exception which caused the error, if any
- */
- @Override
- public void onCameraError(final int errorCode, final Exception e) {
- switch (errorCode) {
- case CameraManager.ERROR_OPENING_CAMERA:
- case CameraManager.ERROR_SHOWING_PREVIEW:
- mErrorToast = R.string.camera_error_opening;
- break;
- case CameraManager.ERROR_INITIALIZING_VIDEO:
- mErrorToast = R.string.camera_error_video_init_fail;
- updateViewState();
- break;
- case CameraManager.ERROR_STORAGE_FAILURE:
- mErrorToast = R.string.camera_error_storage_fail;
- updateViewState();
- break;
- case CameraManager.ERROR_TAKING_PICTURE:
- mErrorToast = R.string.camera_error_failure_taking_picture;
- break;
- default:
- mErrorToast = R.string.camera_error_unknown;
- LogUtil.w(LogUtil.BUGLE_TAG, "Unknown camera error:" + errorCode);
- break;
- }
- showErrorToastIfNeeded();
- }
-
- private void showErrorToastIfNeeded() {
- if (mErrorToast != 0 && mSelected) {
- UiUtils.showToastAtBottom(mErrorToast);
- mErrorToast = 0;
- }
- }
-
- @Override
- public void onCameraChanged() {
- updateViewState();
- }
-
- private void onSwapMode() {
- CameraManager.get().setVideoMode(!CameraManager.get().isVideoMode());
- if (CameraManager.get().isVideoMode()) {
- mMediaPicker.setFullScreen(true);
-
- // For now we start recording immediately
- mCaptureButton.performClick();
- }
- updateViewState();
- }
-
- private void showShutterEffect(final View shutterVisual) {
- final float maxAlpha = getContext().getResources().getFraction(
- R.fraction.camera_shutter_max_alpha, 1 /* base */, 1 /* pBase */);
-
- // Divide by 2 so each half of the animation adds up to the full duration
- final int animationDuration = getContext().getResources().getInteger(
- R.integer.camera_shutter_duration) / 2;
-
- final AnimationSet animation = new AnimationSet(false /* shareInterpolator */);
- final Animation alphaInAnimation = new AlphaAnimation(0.0f, maxAlpha);
- alphaInAnimation.setDuration(animationDuration);
- animation.addAnimation(alphaInAnimation);
-
- final Animation alphaOutAnimation = new AlphaAnimation(maxAlpha, 0.0f);
- alphaOutAnimation.setStartOffset(animationDuration);
- alphaOutAnimation.setDuration(animationDuration);
- animation.addAnimation(alphaOutAnimation);
-
- animation.setAnimationListener(new Animation.AnimationListener() {
- @Override
- public void onAnimationStart(final Animation animation) {
- shutterVisual.setVisibility(View.VISIBLE);
- }
-
- @Override
- public void onAnimationEnd(final Animation animation) {
- shutterVisual.setVisibility(View.GONE);
- }
-
- @Override
- public void onAnimationRepeat(final Animation animation) {
- }
- });
- shutterVisual.startAnimation(animation);
- }
-
- /** Updates the state of the buttons and overlays based on the current state of the view */
- private void updateViewState() {
- if (mView == null) {
- return;
- }
-
- final Context context = getContext();
- if (context == null) {
- // Context is null if the fragment was already removed from the activity
- return;
- }
- final boolean fullScreen = mMediaPicker.isFullScreen();
- final boolean videoMode = CameraManager.get().isVideoMode();
- final boolean isRecording = CameraManager.get().isRecording();
- final boolean isCameraAvailable = isCameraAvailable();
- final Camera.CameraInfo cameraInfo = CameraManager.get().getCameraInfo();
- final boolean frontCamera = cameraInfo != null && cameraInfo.facing ==
- Camera.CameraInfo.CAMERA_FACING_FRONT;
-
- mView.setSystemUiVisibility(
- fullScreen ? View.SYSTEM_UI_FLAG_LOW_PROFILE :
- View.SYSTEM_UI_FLAG_VISIBLE);
-
- mFullScreenButton.setVisibility(!fullScreen ? View.VISIBLE : View.GONE);
- mFullScreenButton.setEnabled(isCameraAvailable);
- mSwapCameraButton.setVisibility(
- fullScreen && !isRecording && CameraManager.get().hasFrontAndBackCamera() ?
- View.VISIBLE : View.GONE);
- mSwapCameraButton.setImageResource(frontCamera ?
- R.drawable.ic_camera_front_light :
- R.drawable.ic_camera_rear_light);
- mSwapCameraButton.setEnabled(isCameraAvailable);
-
- mCancelVideoButton.setVisibility(isRecording ? View.VISIBLE : View.GONE);
- mVideoCounter.setVisibility(isRecording ? View.VISIBLE : View.GONE);
-
- mSwapModeButton.setImageResource(videoMode ?
- R.drawable.ic_mp_camera_small_light :
- R.drawable.ic_mp_video_small_light);
- mSwapModeButton.setContentDescription(context.getString(videoMode ?
- R.string.camera_switch_to_still_mode : R.string.camera_switch_to_video_mode));
- mSwapModeButton.setVisibility(isRecording ? View.GONE : View.VISIBLE);
- mSwapModeButton.setEnabled(isCameraAvailable);
-
- if (isRecording) {
- mCaptureButton.setImageResource(R.drawable.ic_mp_capture_stop_large_light);
- mCaptureButton.setContentDescription(context.getString(
- R.string.camera_stop_recording));
- } else if (videoMode) {
- mCaptureButton.setImageResource(R.drawable.ic_mp_video_large_light);
- mCaptureButton.setContentDescription(context.getString(
- R.string.camera_start_recording));
- } else {
- mCaptureButton.setImageResource(R.drawable.ic_checkmark_large_light);
- mCaptureButton.setContentDescription(context.getString(
- R.string.camera_take_picture));
- }
- mCaptureButton.setEnabled(isCameraAvailable);
- }
-
- @Override
- int getActionBarTitleResId() {
- return 0;
- }
-
- /**
- * Returns if the camera is currently ready camera is loaded and not taking a picture.
- * otherwise we should avoid taking another picture, swapping camera or recording video.
- */
- private boolean isCameraAvailable() {
- return CameraManager.get().isCameraAvailable();
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java b/src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java
deleted file mode 100644
index 64c07b2..0000000
--- a/src/com/android/messaging/ui/mediapicker/CameraMediaChooserView.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.hardware.Camera;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import com.android.messaging.R;
-import com.android.messaging.ui.PersistentInstanceState;
-import com.android.messaging.util.ThreadUtil;
-
-public class CameraMediaChooserView extends FrameLayout implements PersistentInstanceState {
- private static final String KEY_CAMERA_INDEX = "camera_index";
-
- // True if we have at least queued an update to the view tree to support software rendering
- // fallback
- private boolean mIsSoftwareFallbackActive;
-
- public CameraMediaChooserView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected Parcelable onSaveInstanceState() {
- final Bundle bundle = new Bundle();
- bundle.putInt(KEY_CAMERA_INDEX, CameraManager.get().getCameraIndex());
- return bundle;
- }
-
- @Override
- protected void onRestoreInstanceState(final Parcelable state) {
- if (!(state instanceof Bundle)) {
- return;
- }
-
- final Bundle bundle = (Bundle) state;
- CameraManager.get().selectCameraByIndex(bundle.getInt(KEY_CAMERA_INDEX));
- }
-
- @Override
- public Parcelable saveState() {
- return onSaveInstanceState();
- }
-
- @Override
- public void restoreState(final Parcelable restoredState) {
- onRestoreInstanceState(restoredState);
- }
-
- @Override
- public void resetState() {
- CameraManager.get().selectCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
- }
-
- @Override
- protected void onDraw(final Canvas canvas) {
- super.onDraw(canvas);
- // If the canvas isn't hardware accelerated, we have to replace the HardwareCameraPreview
- // with a SoftwareCameraPreview which supports software rendering
- if (!canvas.isHardwareAccelerated() && !mIsSoftwareFallbackActive) {
- mIsSoftwareFallbackActive = true;
- // Post modifying the tree since we can't modify the view tree during a draw pass
- ThreadUtil.getMainThreadHandler().post(new Runnable() {
- @Override
- public void run() {
- final HardwareCameraPreview cameraPreview =
- (HardwareCameraPreview) findViewById(R.id.camera_preview);
- if (cameraPreview == null) {
- return;
- }
- final ViewGroup parent = ((ViewGroup) cameraPreview.getParent());
- final int index = parent.indexOfChild(cameraPreview);
- final SoftwareCameraPreview softwareCameraPreview =
- new SoftwareCameraPreview(getContext());
- // Be sure to remove the hardware view before adding the software view to
- // prevent having 2 camera previews active at the same time
- parent.removeView(cameraPreview);
- parent.addView(softwareCameraPreview, index);
- }
- });
- }
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/CameraPreview.java b/src/com/android/messaging/ui/mediapicker/CameraPreview.java
deleted file mode 100644
index ecac978..0000000
--- a/src/com/android/messaging/ui/mediapicker/CameraPreview.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.hardware.Camera;
-import android.view.View;
-import android.view.View.MeasureSpec;
-import com.android.messaging.util.Assert;
-
-import java.io.IOException;
-
-/**
- * Contains shared code for SoftwareCameraPreview and HardwareCameraPreview, cannot use inheritance
- * because those classes must inherit from separate Views, so those classes delegate calls to this
- * helper class. Specifics for each implementation are in CameraPreviewHost
- */
-public class CameraPreview {
- public interface CameraPreviewHost {
- View getView();
- boolean isValid();
- void startPreview(final Camera camera) throws IOException;
- void onCameraPermissionGranted();
-
- }
-
- private int mCameraWidth = -1;
- private int mCameraHeight = -1;
-
- private final CameraPreviewHost mHost;
-
- public CameraPreview(final CameraPreviewHost host) {
- Assert.notNull(host);
- Assert.notNull(host.getView());
- mHost = host;
- }
-
- public void setSize(final Camera.Size size, final int orientation) {
- switch (orientation) {
- case 0:
- case 180:
- mCameraWidth = size.width;
- mCameraHeight = size.height;
- break;
- case 90:
- case 270:
- default:
- mCameraWidth = size.height;
- mCameraHeight = size.width;
- }
- mHost.getView().requestLayout();
- }
-
- public int getWidthMeasureSpec(final int widthMeasureSpec, final int heightMeasureSpec) {
- if (mCameraHeight >= 0) {
- final int width = View.MeasureSpec.getSize(widthMeasureSpec);
- return MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
- } else {
- return widthMeasureSpec;
- }
- }
-
- public int getHeightMeasureSpec(final int widthMeasureSpec, final int heightMeasureSpec) {
- if (mCameraHeight >= 0) {
- final int orientation = getContext().getResources().getConfiguration().orientation;
- final int width = View.MeasureSpec.getSize(widthMeasureSpec);
- final float aspectRatio = (float) mCameraWidth / (float) mCameraHeight;
- int height;
- if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
- height = (int) (width * aspectRatio);
- } else {
- height = (int) (width / aspectRatio);
- }
- return View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
- } else {
- return heightMeasureSpec;
- }
- }
-
- public void onVisibilityChanged(final int visibility) {
- if (CameraManager.hasCameraPermission()) {
- if (visibility == View.VISIBLE) {
- CameraManager.get().openCamera();
- } else {
- CameraManager.get().closeCamera();
- }
- }
- }
-
- public Context getContext() {
- return mHost.getView().getContext();
- }
-
- public void setOnTouchListener(final View.OnTouchListener listener) {
- mHost.getView().setOnTouchListener(listener);
- }
-
- public int getHeight() {
- return mHost.getView().getHeight();
- }
-
- public void onAttachedToWindow() {
- if (CameraManager.hasCameraPermission()) {
- CameraManager.get().openCamera();
- }
- }
-
- public void onDetachedFromWindow() {
- CameraManager.get().closeCamera();
- }
-
- public void onRestoreInstanceState() {
- if (CameraManager.hasCameraPermission()) {
- CameraManager.get().openCamera();
- }
- }
-
- public void onCameraPermissionGranted() {
- CameraManager.get().openCamera();
- }
-
- /**
- * @return True if the view is valid and prepared for the camera to start showing the preview
- */
- public boolean isValid() {
- return mHost.isValid();
- }
-
- /**
- * Starts the camera preview on the current surface. Abstracts out the differences in API
- * from the CameraManager
- * @throws IOException Which is caught by the CameraManager to display an error
- */
- public void startPreview(final Camera camera) throws IOException {
- mHost.startPreview(camera);
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java b/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java
deleted file mode 100644
index 2c36752..0000000
--- a/src/com/android/messaging/ui/mediapicker/DocumentImagePicker.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.data.PendingAttachmentData;
-import com.android.messaging.ui.UIIntents;
-import com.android.messaging.util.ImageUtils;
-import com.android.messaging.util.SafeAsyncTask;
-
-/**
- * Wraps around the functionalities to allow the user to pick images from the document
- * picker. Instances of this class must be tied to a Fragment which is able to delegate activity
- * result callbacks.
- */
-public class DocumentImagePicker {
-
- /**
- * An interface for a listener that listens for when a document has been picked.
- */
- public interface SelectionListener {
- /**
- * Called when an document is selected from picker. At this point, the file hasn't been
- * actually loaded and staged in the temp directory, so we are passing in a pending
- * MessagePartData, which the consumer should use to display a placeholder image.
- * @param pendingItem a temporary attachment data for showing the placeholder state.
- */
- void onDocumentSelected(PendingAttachmentData pendingItem);
- }
-
- // The owning fragment.
- private final Fragment mFragment;
-
- // The listener on the picker events.
- private final SelectionListener mListener;
-
- private static final String EXTRA_PHOTO_URL = "photo_url";
-
- /**
- * Creates a new instance of DocumentImagePicker.
- * @param activity The activity that owns the picker, or the activity that hosts the owning
- * fragment.
- */
- public DocumentImagePicker(final Fragment fragment,
- final SelectionListener listener) {
- mFragment = fragment;
- mListener = listener;
- }
-
- /**
- * Intent out to open an image/video from document picker.
- */
- public void launchPicker() {
- UIIntents.get().launchDocumentImagePicker(mFragment);
- }
-
- /**
- * Must be called from the fragment/activity's onActivityResult().
- */
- public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
- if (requestCode == UIIntents.REQUEST_PICK_IMAGE_FROM_DOCUMENT_PICKER &&
- resultCode == Activity.RESULT_OK) {
- // Sometimes called after media item has been picked from the document picker.
- String url = data.getStringExtra(EXTRA_PHOTO_URL);
- if (url == null) {
- // we're using the builtin photo picker which supplies the return
- // url as it's "data"
- url = data.getDataString();
- if (url == null) {
- final Bundle extras = data.getExtras();
- if (extras != null) {
- final Uri uri = (Uri) extras.getParcelable(Intent.EXTRA_STREAM);
- if (uri != null) {
- url = uri.toString();
- }
- }
- }
- }
-
- // Guard against null uri cases for when the activity returns a null/invalid intent.
- if (url != null) {
- final Uri uri = Uri.parse(url);
- prepareDocumentForAttachment(uri);
- }
- }
- }
-
- private void prepareDocumentForAttachment(final Uri documentUri) {
- // Notify our listener with a PendingAttachmentData containing the metadata.
- // Asynchronously get the content type for the picked image since
- // ImageUtils.getContentType() potentially involves I/O and can be expensive.
- new SafeAsyncTask<Void, Void, String>() {
- @Override
- protected String doInBackgroundTimed(final Void... params) {
- return ImageUtils.getContentType(
- Factory.get().getApplicationContext().getContentResolver(), documentUri);
- }
-
- @Override
- protected void onPostExecute(final String contentType) {
- // Ask the listener to create a temporary placeholder item to show the progress.
- final PendingAttachmentData pendingItem =
- PendingAttachmentData.createPendingAttachmentData(contentType,
- documentUri);
- mListener.onDocumentSelected(pendingItem);
- }
- }.executeOnThreadPool();
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java b/src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java
deleted file mode 100644
index fda3b19..0000000
--- a/src/com/android/messaging/ui/mediapicker/GalleryGridAdapter.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.CursorAdapter;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.mediapicker.GalleryGridItemView.HostInterface;
-import com.android.messaging.util.Assert;
-
-/**
- * Bridges between the image cursor loaded by GalleryBoundCursorLoader and the GalleryGridView.
- */
-public class GalleryGridAdapter extends CursorAdapter {
- private GalleryGridItemView.HostInterface mGgivHostInterface;
-
- public GalleryGridAdapter(final Context context, final Cursor cursor) {
- super(context, cursor, 0);
- }
-
- public void setHostInterface(final HostInterface ggivHostInterface) {
- mGgivHostInterface = ggivHostInterface;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void bindView(final View view, final Context context, final Cursor cursor) {
- Assert.isTrue(view instanceof GalleryGridItemView);
- final GalleryGridItemView galleryImageView = (GalleryGridItemView) view;
- galleryImageView.bind(cursor, mGgivHostInterface);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public View newView(final Context context, final Cursor cursor, final ViewGroup parent) {
- final LayoutInflater layoutInflater = LayoutInflater.from(context);
- return layoutInflater.inflate(R.layout.gallery_grid_item_view, parent, false);
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java b/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java
deleted file mode 100644
index 3d71fe6..0000000
--- a/src/com/android/messaging/ui/mediapicker/GalleryGridItemView.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.TouchDelegate;
-import android.view.View;
-import android.widget.CheckBox;
-import android.widget.FrameLayout;
-import android.widget.ImageView.ScaleType;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.data.GalleryGridItemData;
-import com.android.messaging.ui.AsyncImageView;
-import com.android.messaging.ui.ConversationDrawables;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * Shows an item in the gallery picker grid view. Hosts an FileImageView with a checkbox.
- */
-public class GalleryGridItemView extends FrameLayout {
- /**
- * Implemented by the owner of this GalleryGridItemView instance to communicate on media
- * picking and selection events.
- */
- public interface HostInterface {
- void onItemClicked(View view, GalleryGridItemData data, boolean longClick);
- boolean isItemSelected(GalleryGridItemData data);
- boolean isMultiSelectEnabled();
- }
-
- @VisibleForTesting
- GalleryGridItemData mData;
- private AsyncImageView mImageView;
- private CheckBox mCheckBox;
- private HostInterface mHostInterface;
- private final OnClickListener mOnClickListener = new OnClickListener() {
- @Override
- public void onClick(final View v) {
- mHostInterface.onItemClicked(GalleryGridItemView.this, mData, false /*longClick*/);
- }
- };
-
- public GalleryGridItemView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mData = DataModel.get().createGalleryGridItemData();
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mImageView = (AsyncImageView) findViewById(R.id.image);
- mCheckBox = (CheckBox) findViewById(R.id.checkbox);
- mCheckBox.setOnClickListener(mOnClickListener);
- setOnClickListener(mOnClickListener);
- final OnLongClickListener longClickListener = new OnLongClickListener() {
- @Override
- public boolean onLongClick(final View v) {
- mHostInterface.onItemClicked(v, mData, true /* longClick */);
- return true;
- }
- };
- setOnLongClickListener(longClickListener);
- mCheckBox.setOnLongClickListener(longClickListener);
- addOnLayoutChangeListener(new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- // Enlarge the clickable region for the checkbox to fill the entire view.
- final Rect region = new Rect(0, 0, getWidth(), getHeight());
- setTouchDelegate(new TouchDelegate(region, mCheckBox) {
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- setPressed(true);
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- setPressed(false);
- break;
- }
- return super.onTouchEvent(event);
- }
- });
- }
- });
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- // The grid view auto-fit the columns, so we want to let the height match the width
- // to make the image square.
- super.onMeasure(widthMeasureSpec, widthMeasureSpec);
- }
-
- public void bind(final Cursor cursor, final HostInterface hostInterface) {
- final int desiredSize = getResources()
- .getDimensionPixelSize(R.dimen.gallery_image_cell_size);
- mData.bind(cursor, desiredSize, desiredSize);
- mHostInterface = hostInterface;
- updateViewState();
- }
-
- private void updateViewState() {
- updateImageView();
- if (mHostInterface.isMultiSelectEnabled() && !mData.isDocumentPickerItem()) {
- mCheckBox.setVisibility(VISIBLE);
- mCheckBox.setClickable(true);
- mCheckBox.setChecked(mHostInterface.isItemSelected(mData));
- } else {
- mCheckBox.setVisibility(GONE);
- mCheckBox.setClickable(false);
- }
- }
-
- private void updateImageView() {
- if (mData.isDocumentPickerItem()) {
- mImageView.setScaleType(ScaleType.CENTER);
- setBackgroundColor(ConversationDrawables.get().getConversationThemeColor());
- mImageView.setImageResourceId(null);
- mImageView.setImageResource(R.drawable.ic_photo_library_light);
- mImageView.setContentDescription(getResources().getString(
- R.string.pick_image_from_document_library_content_description));
- } else {
- mImageView.setScaleType(ScaleType.CENTER_CROP);
- setBackgroundColor(getResources().getColor(R.color.gallery_image_default_background));
- mImageView.setImageResourceId(mData.getImageRequestDescriptor());
- final long dateSeconds = mData.getDateSeconds();
- final boolean isValidDate = (dateSeconds > 0);
- final int templateId = isValidDate ?
- R.string.mediapicker_gallery_image_item_description :
- R.string.mediapicker_gallery_image_item_description_no_date;
- String contentDescription = String.format(getResources().getString(templateId),
- dateSeconds * TimeUnit.SECONDS.toMillis(1));
- mImageView.setContentDescription(contentDescription);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryGridView.java b/src/com/android/messaging/ui/mediapicker/GalleryGridView.java
deleted file mode 100644
index a5a7dad..0000000
--- a/src/com/android/messaging/ui/mediapicker/GalleryGridView.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.support.v4.util.ArrayMap;
-import android.util.AttributeSet;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.binding.ImmutableBindingRef;
-import com.android.messaging.datamodel.data.DraftMessageData;
-import com.android.messaging.datamodel.data.GalleryGridItemData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageDataListener;
-import com.android.messaging.ui.PersistentInstanceState;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * Shows a list of galley images from external storage in a GridView with multi-select
- * capabilities, and with the option to intent out to a standalone image picker.
- */
-public class GalleryGridView extends MediaPickerGridView implements
- GalleryGridItemView.HostInterface,
- PersistentInstanceState,
- DraftMessageDataListener {
- /**
- * Implemented by the owner of this GalleryGridView instance to communicate on image
- * picking and multi-image selection events.
- */
- public interface GalleryGridViewListener {
- void onDocumentPickerItemClicked();
- void onItemSelected(MessagePartData item);
- void onItemUnselected(MessagePartData item);
- void onConfirmSelection();
- void onUpdate();
- }
-
- private GalleryGridViewListener mListener;
-
- // TODO: Consider putting this into the data model object if we add more states.
- private final ArrayMap<Uri, MessagePartData> mSelectedImages;
- private boolean mIsMultiSelectMode = false;
- private ImmutableBindingRef<DraftMessageData> mDraftMessageDataModel;
-
- public GalleryGridView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mSelectedImages = new ArrayMap<Uri, MessagePartData>();
- }
-
- public void setHostInterface(final GalleryGridViewListener hostInterface) {
- mListener = hostInterface;
- }
-
- public void setDraftMessageDataModel(final BindingBase<DraftMessageData> dataModel) {
- mDraftMessageDataModel = BindingBase.createBindingReference(dataModel);
- mDraftMessageDataModel.getData().addListener(this);
- }
-
- @Override
- public void onItemClicked(final View view, final GalleryGridItemData data,
- final boolean longClick) {
- if (data.isDocumentPickerItem()) {
- mListener.onDocumentPickerItemClicked();
- } else if (ContentType.isMediaType(data.getContentType())) {
- if (longClick) {
- // Turn on multi-select mode when an item is long-pressed.
- setMultiSelectEnabled(true);
- }
-
- final Rect startRect = new Rect();
- view.getGlobalVisibleRect(startRect);
- if (isMultiSelectEnabled()) {
- toggleItemSelection(startRect, data);
- } else {
- mListener.onItemSelected(data.constructMessagePartData(startRect));
- }
- } else {
- LogUtil.w(LogUtil.BUGLE_TAG,
- "Selected item has invalid contentType " + data.getContentType());
- }
- }
-
- @Override
- public boolean isItemSelected(final GalleryGridItemData data) {
- return mSelectedImages.containsKey(data.getImageUri());
- }
-
- int getSelectionCount() {
- return mSelectedImages.size();
- }
-
- @Override
- public boolean isMultiSelectEnabled() {
- return mIsMultiSelectMode;
- }
-
- private void toggleItemSelection(final Rect startRect, final GalleryGridItemData data) {
- Assert.isTrue(isMultiSelectEnabled());
- if (isItemSelected(data)) {
- final MessagePartData item = mSelectedImages.remove(data.getImageUri());
- mListener.onItemUnselected(item);
- if (mSelectedImages.size() == 0) {
- // No image is selected any more, turn off multi-select mode.
- setMultiSelectEnabled(false);
- }
- } else {
- final MessagePartData item = data.constructMessagePartData(startRect);
- mSelectedImages.put(data.getImageUri(), item);
- mListener.onItemSelected(item);
- }
- invalidateViews();
- }
-
- private void toggleMultiSelect() {
- mIsMultiSelectMode = !mIsMultiSelectMode;
- invalidateViews();
- }
-
- private void setMultiSelectEnabled(final boolean enabled) {
- if (mIsMultiSelectMode != enabled) {
- toggleMultiSelect();
- }
- }
-
- private boolean canToggleMultiSelect() {
- // We allow the user to toggle multi-select mode only when nothing has selected. If
- // something has been selected, we show a confirm button instead.
- return mSelectedImages.size() == 0;
- }
-
- public void onCreateOptionsMenu(final MenuInflater inflater, final Menu menu) {
- inflater.inflate(R.menu.gallery_picker_menu, menu);
- final MenuItem toggleMultiSelect = menu.findItem(R.id.action_multiselect);
- final MenuItem confirmMultiSelect = menu.findItem(R.id.action_confirm_multiselect);
- final boolean canToggleMultiSelect = canToggleMultiSelect();
- toggleMultiSelect.setVisible(canToggleMultiSelect);
- confirmMultiSelect.setVisible(!canToggleMultiSelect);
- }
-
- public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case R.id.action_multiselect:
- Assert.isTrue(canToggleMultiSelect());
- toggleMultiSelect();
- return true;
-
- case R.id.action_confirm_multiselect:
- Assert.isTrue(!canToggleMultiSelect());
- mListener.onConfirmSelection();
- return true;
- }
- return false;
- }
-
-
- @Override
- public void onDraftChanged(final DraftMessageData data, final int changeFlags) {
- mDraftMessageDataModel.ensureBound(data);
- // Whenever attachment changed, refresh selection state to remove those that are not
- // selected.
- if ((changeFlags & DraftMessageData.ATTACHMENTS_CHANGED) ==
- DraftMessageData.ATTACHMENTS_CHANGED) {
- refreshImageSelectionStateOnAttachmentChange();
- }
- }
-
- @Override
- public void onDraftAttachmentLimitReached(final DraftMessageData data) {
- mDraftMessageDataModel.ensureBound(data);
- // Whenever draft attachment limit is reach, refresh selection state to remove those
- // not actually added to draft.
- refreshImageSelectionStateOnAttachmentChange();
- }
-
- @Override
- public void onDraftAttachmentLoadFailed() {
- // Nothing to do since the failed attachment gets removed automatically.
- }
-
- private void refreshImageSelectionStateOnAttachmentChange() {
- boolean changed = false;
- final Iterator<Map.Entry<Uri, MessagePartData>> iterator =
- mSelectedImages.entrySet().iterator();
- while (iterator.hasNext()) {
- Map.Entry<Uri, MessagePartData> entry = iterator.next();
- if (!mDraftMessageDataModel.getData().containsAttachment(entry.getKey())) {
- iterator.remove();
- changed = true;
- }
- }
-
- if (changed) {
- mListener.onUpdate();
- invalidateViews();
- }
- }
-
- @Override // PersistentInstanceState
- public Parcelable saveState() {
- return onSaveInstanceState();
- }
-
- @Override // PersistentInstanceState
- public void restoreState(final Parcelable restoredState) {
- onRestoreInstanceState(restoredState);
- invalidateViews();
- }
-
- @Override
- public Parcelable onSaveInstanceState() {
- final Parcelable superState = super.onSaveInstanceState();
- final SavedState savedState = new SavedState(superState);
- savedState.isMultiSelectMode = mIsMultiSelectMode;
- savedState.selectedImages = mSelectedImages.values()
- .toArray(new MessagePartData[mSelectedImages.size()]);
- return savedState;
- }
-
- @Override
- public void onRestoreInstanceState(final Parcelable state) {
- if (!(state instanceof SavedState)) {
- super.onRestoreInstanceState(state);
- return;
- }
-
- final SavedState savedState = (SavedState) state;
- super.onRestoreInstanceState(savedState.getSuperState());
- mIsMultiSelectMode = savedState.isMultiSelectMode;
- mSelectedImages.clear();
- for (int i = 0; i < savedState.selectedImages.length; i++) {
- final MessagePartData selectedImage = savedState.selectedImages[i];
- mSelectedImages.put(selectedImage.getContentUri(), selectedImage);
- }
- }
-
- @Override // PersistentInstanceState
- public void resetState() {
- mSelectedImages.clear();
- mIsMultiSelectMode = false;
- invalidateViews();
- }
-
- public static class SavedState extends BaseSavedState {
- boolean isMultiSelectMode;
- MessagePartData[] selectedImages;
-
- SavedState(final Parcelable superState) {
- super(superState);
- }
-
- private SavedState(final Parcel in) {
- super(in);
- isMultiSelectMode = in.readInt() == 1 ? true : false;
-
- // Read parts
- final int partCount = in.readInt();
- selectedImages = new MessagePartData[partCount];
- for (int i = 0; i < partCount; i++) {
- selectedImages[i] = ((MessagePartData) in.readParcelable(
- MessagePartData.class.getClassLoader()));
- }
- }
-
- @Override
- public void writeToParcel(final Parcel out, final int flags) {
- super.writeToParcel(out, flags);
- out.writeInt(isMultiSelectMode ? 1 : 0);
-
- // Write parts
- out.writeInt(selectedImages.length);
- for (final MessagePartData image : selectedImages) {
- out.writeParcelable(image, flags);
- }
- }
-
- public static final Parcelable.Creator<SavedState> CREATOR =
- new Parcelable.Creator<SavedState>() {
- @Override
- public SavedState createFromParcel(final Parcel in) {
- return new SavedState(in);
- }
- @Override
- public SavedState[] newArray(final int size) {
- return new SavedState[size];
- }
- };
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java b/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java
deleted file mode 100644
index 9422386..0000000
--- a/src/com/android/messaging/ui/mediapicker/GalleryMediaChooser.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.Manifest;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.database.MatrixCursor;
-import android.database.MergeCursor;
-import android.support.v7.app.ActionBar;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.data.GalleryGridItemData;
-import com.android.messaging.datamodel.data.MediaPickerData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.MediaPickerData.MediaPickerDataListener;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.OsUtil;
-
-/**
- * Chooser which allows the user to select one or more existing images or videos
- */
-class GalleryMediaChooser extends MediaChooser implements
- GalleryGridView.GalleryGridViewListener, MediaPickerDataListener {
- private final GalleryGridAdapter mAdapter;
- private GalleryGridView mGalleryGridView;
- private View mMissingPermissionView;
-
- GalleryMediaChooser(final MediaPicker mediaPicker) {
- super(mediaPicker);
- mAdapter = new GalleryGridAdapter(Factory.get().getApplicationContext(), null);
- }
-
- @Override
- public int getSupportedMediaTypes() {
- return MediaPicker.MEDIA_TYPE_IMAGE | MediaPicker.MEDIA_TYPE_VIDEO;
- }
-
- @Override
- public View destroyView() {
- mGalleryGridView.setAdapter(null);
- mAdapter.setHostInterface(null);
- // The loader is started only if startMediaPickerDataLoader() is called
- if (OsUtil.hasStoragePermission()) {
- mBindingRef.getData().destroyLoader(MediaPickerData.GALLERY_IMAGE_LOADER);
- }
- return super.destroyView();
- }
-
- @Override
- public int getIconResource() {
- return R.drawable.ic_image_light;
- }
-
- @Override
- public int getIconDescriptionResource() {
- return R.string.mediapicker_galleryChooserDescription;
- }
-
- @Override
- public boolean canSwipeDown() {
- return mGalleryGridView.canSwipeDown();
- }
-
- @Override
- public void onItemSelected(final MessagePartData item) {
- mMediaPicker.dispatchItemsSelected(item, !mGalleryGridView.isMultiSelectEnabled());
- }
-
- @Override
- public void onItemUnselected(final MessagePartData item) {
- mMediaPicker.dispatchItemUnselected(item);
- }
-
- @Override
- public void onConfirmSelection() {
- // The user may only confirm if multiselect is enabled.
- Assert.isTrue(mGalleryGridView.isMultiSelectEnabled());
- mMediaPicker.dispatchConfirmItemSelection();
- }
-
- @Override
- public void onUpdate() {
- mMediaPicker.invalidateOptionsMenu();
- }
-
- @Override
- public void onCreateOptionsMenu(final MenuInflater inflater, final Menu menu) {
- if (mView != null) {
- mGalleryGridView.onCreateOptionsMenu(inflater, menu);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- return (mView != null) ? mGalleryGridView.onOptionsItemSelected(item) : false;
- }
-
- @Override
- protected View createView(final ViewGroup container) {
- final LayoutInflater inflater = getLayoutInflater();
- final View view = inflater.inflate(
- R.layout.mediapicker_image_chooser,
- container /* root */,
- false /* attachToRoot */);
-
- mGalleryGridView = (GalleryGridView) view.findViewById(R.id.gallery_grid_view);
- mAdapter.setHostInterface(mGalleryGridView);
- mGalleryGridView.setAdapter(mAdapter);
- mGalleryGridView.setHostInterface(this);
- mGalleryGridView.setDraftMessageDataModel(mMediaPicker.getDraftMessageDataModel());
- if (OsUtil.hasStoragePermission()) {
- startMediaPickerDataLoader();
- }
-
- mMissingPermissionView = view.findViewById(R.id.missing_permission_view);
- updateForPermissionState(OsUtil.hasStoragePermission());
- return view;
- }
-
- @Override
- int getActionBarTitleResId() {
- return R.string.mediapicker_gallery_title;
- }
-
- @Override
- public void onDocumentPickerItemClicked() {
- mMediaPicker.launchDocumentPicker();
- }
-
- @Override
- void updateActionBar(final ActionBar actionBar) {
- super.updateActionBar(actionBar);
- if (mGalleryGridView == null) {
- return;
- }
- final int selectionCount = mGalleryGridView.getSelectionCount();
- if (selectionCount > 0 && mGalleryGridView.isMultiSelectEnabled()) {
- actionBar.setTitle(getContext().getResources().getString(
- R.string.mediapicker_gallery_title_selection,
- selectionCount));
- }
- }
-
- @Override
- public void onMediaPickerDataUpdated(final MediaPickerData mediaPickerData, final Object data,
- final int loaderId) {
- mBindingRef.ensureBound(mediaPickerData);
- Assert.equals(MediaPickerData.GALLERY_IMAGE_LOADER, loaderId);
- Cursor rawCursor = null;
- if (data instanceof Cursor) {
- rawCursor = (Cursor) data;
- }
- // Before delivering the cursor, wrap around the local gallery cursor
- // with an extra item for document picker integration in the front.
- final MatrixCursor specialItemsCursor =
- new MatrixCursor(GalleryGridItemData.SPECIAL_ITEM_COLUMNS);
- specialItemsCursor.addRow(new Object[] { GalleryGridItemData.ID_DOCUMENT_PICKER_ITEM });
- final MergeCursor cursor =
- new MergeCursor(new Cursor[] { specialItemsCursor, rawCursor });
- mAdapter.swapCursor(cursor);
- }
-
- @Override
- public void onResume() {
- if (OsUtil.hasStoragePermission()) {
- // Work around a bug in MediaStore where cursors querying the Files provider don't get
- // updated for changes to Images.Media or Video.Media.
- startMediaPickerDataLoader();
- }
- }
-
- @Override
- protected void setSelected(final boolean selected) {
- super.setSelected(selected);
- if (selected && !OsUtil.hasStoragePermission()) {
- mMediaPicker.requestPermissions(
- new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
- MediaPicker.GALLERY_PERMISSION_REQUEST_CODE);
- }
- }
-
- private void startMediaPickerDataLoader() {
- mBindingRef.getData().startLoader(MediaPickerData.GALLERY_IMAGE_LOADER, mBindingRef, null,
- this);
- }
-
- @Override
- protected void onRequestPermissionsResult(
- final int requestCode, final String permissions[], final int[] grantResults) {
- if (requestCode == MediaPicker.GALLERY_PERMISSION_REQUEST_CODE) {
- final boolean permissionGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
- if (permissionGranted) {
- startMediaPickerDataLoader();
- }
- updateForPermissionState(permissionGranted);
- }
- }
-
- private void updateForPermissionState(final boolean granted) {
- // onRequestPermissionsResult can sometimes get called before createView().
- if (mGalleryGridView == null) {
- return;
- }
-
- mGalleryGridView.setVisibility(granted ? View.VISIBLE : View.GONE);
- mMissingPermissionView.setVisibility(granted ? View.GONE : View.VISIBLE);
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java b/src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java
deleted file mode 100644
index 45d9579..0000000
--- a/src/com/android/messaging/ui/mediapicker/HardwareCameraPreview.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.content.Context;
-import android.graphics.SurfaceTexture;
-import android.hardware.Camera;
-import android.os.Parcelable;
-import android.util.AttributeSet;
-import android.view.TextureView;
-import android.view.View;
-
-import java.io.IOException;
-
-/**
- * A hardware accelerated preview texture for the camera. This is the preferred CameraPreview
- * because it animates smoother. When hardware acceleration isn't available, SoftwareCameraPreview
- * is used.
- *
- * There is a significant amount of duplication between HardwareCameraPreview and
- * SoftwareCameraPreview which we can't easily share due to a lack of multiple inheritance, The
- * implementations of the shared methods are delegated to CameraPreview
- */
-public class HardwareCameraPreview extends TextureView implements CameraPreview.CameraPreviewHost {
- private CameraPreview mPreview;
-
- public HardwareCameraPreview(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- mPreview = new CameraPreview(this);
- setSurfaceTextureListener(new SurfaceTextureListener() {
- @Override
- public void onSurfaceTextureAvailable(final SurfaceTexture surfaceTexture, final int i, final int i2) {
- CameraManager.get().setSurface(mPreview);
- }
-
- @Override
- public void onSurfaceTextureSizeChanged(final SurfaceTexture surfaceTexture, final int i, final int i2) {
- CameraManager.get().setSurface(mPreview);
- }
-
- @Override
- public boolean onSurfaceTextureDestroyed(final SurfaceTexture surfaceTexture) {
- CameraManager.get().setSurface(null);
- return true;
- }
-
- @Override
- public void onSurfaceTextureUpdated(final SurfaceTexture surfaceTexture) {
- CameraManager.get().setSurface(mPreview);
- }
- });
- }
-
- @Override
- protected void onVisibilityChanged(final View changedView, final int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- mPreview.onVisibilityChanged(visibility);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mPreview.onDetachedFromWindow();
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mPreview.onAttachedToWindow();
- }
-
- @Override
- protected void onRestoreInstanceState(final Parcelable state) {
- super.onRestoreInstanceState(state);
- mPreview.onRestoreInstanceState();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- widthMeasureSpec = mPreview.getWidthMeasureSpec(widthMeasureSpec, heightMeasureSpec);
- heightMeasureSpec = mPreview.getHeightMeasureSpec(widthMeasureSpec, heightMeasureSpec);
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public View getView() {
- return this;
- }
-
- @Override
- public boolean isValid() {
- return getSurfaceTexture() != null;
- }
-
- @Override
- public void startPreview(final Camera camera) throws IOException {
- camera.setPreviewTexture(getSurfaceTexture());
- }
-
- @Override
- public void onCameraPermissionGranted() {
- mPreview.onCameraPermissionGranted();
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/ImagePersistTask.java b/src/com/android/messaging/ui/mediapicker/ImagePersistTask.java
deleted file mode 100644
index 637eb84..0000000
--- a/src/com/android/messaging/ui/mediapicker/ImagePersistTask.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.net.Uri;
-
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.SafeAsyncTask;
-import com.android.messaging.util.exif.ExifInterface;
-import com.android.messaging.util.exif.ExifTag;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-public class ImagePersistTask extends SafeAsyncTask<Void, Void, Void> {
- private static final String JPEG_EXTENSION = "jpg";
- private static final String TAG = LogUtil.BUGLE_TAG;
-
- private int mWidth;
- private int mHeight;
- private final float mHeightPercent;
- private final byte[] mBytes;
- private final Context mContext;
- private final CameraManager.MediaCallback mCallback;
- private Uri mOutputUri;
- private Exception mException;
-
- public ImagePersistTask(
- final int width,
- final int height,
- final float heightPercent,
- final byte[] bytes,
- final Context context,
- final CameraManager.MediaCallback callback) {
- Assert.isTrue(heightPercent >= 0 && heightPercent <= 1);
- Assert.notNull(bytes);
- Assert.notNull(context);
- Assert.notNull(callback);
- mWidth = width;
- mHeight = height;
- mHeightPercent = heightPercent;
- mBytes = bytes;
- mContext = context;
- mCallback = callback;
- // TODO: We probably want to store directly in MMS storage to prevent this
- // intermediate step
- mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(JPEG_EXTENSION);
- }
-
- @Override
- protected Void doInBackgroundTimed(final Void... params) {
- OutputStream outputStream = null;
- Bitmap bitmap = null;
- Bitmap clippedBitmap = null;
- try {
- outputStream =
- mContext.getContentResolver().openOutputStream(mOutputUri);
- if (mHeightPercent != 1.0f) {
- int orientation = android.media.ExifInterface.ORIENTATION_UNDEFINED;
- final ExifInterface exifInterface = new ExifInterface();
- try {
- exifInterface.readExif(mBytes);
- final Integer orientationValue =
- exifInterface.getTagIntValue(ExifInterface.TAG_ORIENTATION);
- if (orientationValue != null) {
- orientation = orientationValue.intValue();
- }
- // The thumbnail is of the full image, but we're cropping it, so just clear
- // the thumbnail
- exifInterface.setCompressedThumbnail((byte[]) null);
- } catch (IOException e) {
- // Couldn't get exif tags, not the end of the world
- }
- bitmap = BitmapFactory.decodeByteArray(mBytes, 0, mBytes.length);
- final int clippedWidth;
- final int clippedHeight;
- if (ExifInterface.getOrientationParams(orientation).invertDimensions) {
- Assert.equals(mWidth, bitmap.getHeight());
- Assert.equals(mHeight, bitmap.getWidth());
- clippedWidth = (int) (mHeight * mHeightPercent);
- clippedHeight = mWidth;
- } else {
- Assert.equals(mWidth, bitmap.getWidth());
- Assert.equals(mHeight, bitmap.getHeight());
- clippedWidth = mWidth;
- clippedHeight = (int) (mHeight * mHeightPercent);
- }
- final int offsetTop = (bitmap.getHeight() - clippedHeight) / 2;
- final int offsetLeft = (bitmap.getWidth() - clippedWidth) / 2;
- mWidth = clippedWidth;
- mHeight = clippedHeight;
- clippedBitmap = Bitmap.createBitmap(clippedWidth, clippedHeight,
- Bitmap.Config.ARGB_8888);
- clippedBitmap.setDensity(bitmap.getDensity());
- final Canvas clippedBitmapCanvas = new Canvas(clippedBitmap);
- final Matrix matrix = new Matrix();
- matrix.postTranslate(-offsetLeft, -offsetTop);
- clippedBitmapCanvas.drawBitmap(bitmap, matrix, null /* paint */);
- clippedBitmapCanvas.save();
- // EXIF data can take a big chunk of the file size and is often cleared by the
- // carrier, only store orientation since that's critical
- ExifTag orientationTag = exifInterface.getTag(ExifInterface.TAG_ORIENTATION);
- exifInterface.clearExif();
- exifInterface.setTag(orientationTag);
- exifInterface.writeExif(clippedBitmap, outputStream);
- } else {
- outputStream.write(mBytes);
- }
- } catch (final IOException e) {
- mOutputUri = null;
- mException = e;
- LogUtil.e(TAG, "Unable to persist image to temp storage " + e);
- } finally {
- if (bitmap != null) {
- bitmap.recycle();
- }
-
- if (clippedBitmap != null) {
- clippedBitmap.recycle();
- }
-
- if (outputStream != null) {
- try {
- outputStream.flush();
- } catch (final IOException e) {
- mOutputUri = null;
- mException = e;
- LogUtil.e(TAG, "error trying to flush and close the outputStream" + e);
- } finally {
- try {
- outputStream.close();
- } catch (final IOException e) {
- // Do nothing.
- }
- }
- }
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(final Void aVoid) {
- if (mOutputUri != null) {
- mCallback.onMediaReady(mOutputUri, ContentType.IMAGE_JPEG, mWidth, mHeight);
- } else {
- Assert.notNull(mException);
- mCallback.onMediaFailed(mException);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/LevelTrackingMediaRecorder.java b/src/com/android/messaging/ui/mediapicker/LevelTrackingMediaRecorder.java
deleted file mode 100644
index 06730a3..0000000
--- a/src/com/android/messaging/ui/mediapicker/LevelTrackingMediaRecorder.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.media.MediaRecorder;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.SafeAsyncTask;
-import com.android.messaging.util.UiUtils;
-
-import java.io.IOException;
-
-/**
- * Wraps around the functionalities of MediaRecorder, performs routine setup for audio recording
- * and updates the audio level to be displayed in UI.
- *
- * During the start and end of a recording session, we kick off a thread that polls for audio
- * levels, and updates the thread-safe AudioLevelSource instance. Consumers may bind to the
- * sound level by either polling from the level source, or register for a level change callback
- * on the level source object. In Bugle, the UI element (SoundLevels) polls for the sound level
- * on the UI thread by using animation ticks and invalidating itself.
- *
- * Aside from tracking sound levels, this also encapsulates the functionality to save the file
- * to the scratch space. The saved file is returned by calling stopRecording().
- */
-public class LevelTrackingMediaRecorder {
- // We refresh sound level every 100ms during a recording session.
- private static final int REFRESH_INTERVAL_MILLIS = 100;
-
- // The native amplitude returned from MediaRecorder ranges from 0~32768 (unfortunately, this
- // is not a constant that's defined anywhere, but the framework's Recorder app is using the
- // same hard-coded number). Therefore, a constant is needed in order to make it 0~100.
- private static final int MAX_AMPLITUDE_FACTOR = 32768 / 100;
-
- // We want to limit the max audio file size by the max message size allowed by MmsConfig,
- // plus multiplied by this fudge ratio to guarantee that we don't go over limit.
- private static final float MAX_SIZE_RATIO = 0.8f;
-
- // Default recorder settings for Bugle.
- // TODO: Do we want these to be tweakable?
- private static final int MEDIA_RECORDER_AUDIO_SOURCE = MediaRecorder.AudioSource.MIC;
- private static final int MEDIA_RECORDER_OUTPUT_FORMAT = MediaRecorder.OutputFormat.THREE_GPP;
- private static final int MEDIA_RECORDER_AUDIO_ENCODER = MediaRecorder.AudioEncoder.AMR_NB;
-
- private final AudioLevelSource mLevelSource;
- private Thread mRefreshLevelThread;
- private MediaRecorder mRecorder;
- private Uri mOutputUri;
- private ParcelFileDescriptor mOutputFD;
-
- public LevelTrackingMediaRecorder() {
- mLevelSource = new AudioLevelSource();
- }
-
- public AudioLevelSource getLevelSource() {
- return mLevelSource;
- }
-
- /**
- * @return if we are currently in a recording session.
- */
- public boolean isRecording() {
- return mRecorder != null;
- }
-
- /**
- * Start a new recording session.
- * @return true if a session is successfully started; false if something went wrong or if
- * we are already recording.
- */
- public boolean startRecording(final MediaRecorder.OnErrorListener errorListener,
- final MediaRecorder.OnInfoListener infoListener, int maxSize) {
- synchronized (LevelTrackingMediaRecorder.class) {
- if (mRecorder == null) {
- mOutputUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(
- ContentType.THREE_GPP_EXTENSION);
- mRecorder = new MediaRecorder();
- try {
- // The scratch space file is a Uri, however MediaRecorder
- // API only accepts absolute FD's. Therefore, get the
- // FileDescriptor from the content resolver to ensure the
- // directory is created and get the file path to output the
- // audio to.
- maxSize *= MAX_SIZE_RATIO;
- mOutputFD = Factory.get().getApplicationContext()
- .getContentResolver().openFileDescriptor(mOutputUri, "w");
- mRecorder.setAudioSource(MEDIA_RECORDER_AUDIO_SOURCE);
- mRecorder.setOutputFormat(MEDIA_RECORDER_OUTPUT_FORMAT);
- mRecorder.setAudioEncoder(MEDIA_RECORDER_AUDIO_ENCODER);
- mRecorder.setOutputFile(mOutputFD.getFileDescriptor());
- mRecorder.setMaxFileSize(maxSize);
- mRecorder.setOnErrorListener(errorListener);
- mRecorder.setOnInfoListener(infoListener);
- mRecorder.prepare();
- mRecorder.start();
- startTrackingSoundLevel();
- return true;
- } catch (final Exception e) {
- // There may be a device failure or I/O failure, record the error but
- // don't fail.
- LogUtil.e(LogUtil.BUGLE_TAG, "Something went wrong when starting " +
- "media recorder. " + e);
- UiUtils.showToastAtBottom(R.string.audio_recording_start_failed);
- stopRecording();
- }
- } else {
- Assert.fail("Trying to start a new recording session while already recording!");
- }
- return false;
- }
- }
-
- /**
- * Stop the current recording session.
- * @return the Uri of the output file, or null if not currently recording.
- */
- public Uri stopRecording() {
- synchronized (LevelTrackingMediaRecorder.class) {
- if (mRecorder != null) {
- try {
- mRecorder.stop();
- } catch (final RuntimeException ex) {
- // This may happen when the recording is too short, so just drop the recording
- // in this case.
- LogUtil.w(LogUtil.BUGLE_TAG, "Something went wrong when stopping " +
- "media recorder. " + ex);
- if (mOutputUri != null) {
- final Uri outputUri = mOutputUri;
- SafeAsyncTask.executeOnThreadPool(new Runnable() {
- @Override
- public void run() {
- Factory.get().getApplicationContext().getContentResolver().delete(
- outputUri, null, null);
- }
- });
- mOutputUri = null;
- }
- } finally {
- mRecorder.release();
- mRecorder = null;
- }
- } else {
- Assert.fail("Not currently recording!");
- return null;
- }
- }
-
- if (mOutputFD != null) {
- try {
- mOutputFD.close();
- } catch (final IOException e) {
- // Nothing to do
- }
- mOutputFD = null;
- }
-
- stopTrackingSoundLevel();
- return mOutputUri;
- }
-
- private int getAmplitude() {
- synchronized (LevelTrackingMediaRecorder.class) {
- if (mRecorder != null) {
- final int maxAmplitude = mRecorder.getMaxAmplitude() / MAX_AMPLITUDE_FACTOR;
- return Math.min(maxAmplitude, 100);
- } else {
- return 0;
- }
- }
- }
-
- private void startTrackingSoundLevel() {
- stopTrackingSoundLevel();
- mRefreshLevelThread = new Thread() {
- @Override
- public void run() {
- try {
- while (true) {
- synchronized (LevelTrackingMediaRecorder.class) {
- if (mRecorder != null) {
- mLevelSource.setSpeechLevel(getAmplitude());
- } else {
- // The recording session is over, finish the thread.
- return;
- }
- }
- Thread.sleep(REFRESH_INTERVAL_MILLIS);
- }
- } catch (final InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
- };
- mRefreshLevelThread.start();
- }
-
- private void stopTrackingSoundLevel() {
- if (mRefreshLevelThread != null && mRefreshLevelThread.isAlive()) {
- mRefreshLevelThread.interrupt();
- mRefreshLevelThread = null;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/MediaChooser.java b/src/com/android/messaging/ui/mediapicker/MediaChooser.java
deleted file mode 100644
index 9ac0d1b..0000000
--- a/src/com/android/messaging/ui/mediapicker/MediaChooser.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.app.FragmentManager;
-import android.content.Context;
-import android.support.v7.app.ActionBar;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-
-import com.android.messaging.R;
-import com.android.messaging.datamodel.binding.ImmutableBindingRef;
-import com.android.messaging.datamodel.data.MediaPickerData;
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
-import com.android.messaging.ui.BasePagerViewHolder;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.OsUtil;
-
-abstract class MediaChooser extends BasePagerViewHolder
- implements DraftMessageSubscriptionDataProvider {
- /** The media picker that the chooser is hosted in */
- protected final MediaPicker mMediaPicker;
-
- /** Referencing the main media picker binding to perform data loading */
- protected final ImmutableBindingRef<MediaPickerData> mBindingRef;
-
- /** True if this is the selected chooser */
- protected boolean mSelected;
-
- /** True if this chooser is open */
- protected boolean mOpen;
-
- /** The button to show in the tab strip */
- private ImageButton mTabButton;
-
- /** Used by subclasses to indicate that no loader is required from the data model in order for
- * this chooser to function.
- */
- public static final int NO_LOADER_REQUIRED = -1;
-
- /**
- * Initializes a new instance of the Chooser class
- * @param mediaPicker The media picker that the chooser is hosted in
- */
- MediaChooser(final MediaPicker mediaPicker) {
- Assert.notNull(mediaPicker);
- mMediaPicker = mediaPicker;
- mBindingRef = mediaPicker.getMediaPickerDataBinding();
- mSelected = false;
- }
-
- protected void setSelected(final boolean selected) {
- mSelected = selected;
- if (selected) {
- // If we're selected, it must be open
- mOpen = true;
- }
- if (mTabButton != null) {
- mTabButton.setSelected(selected);
- mTabButton.setAlpha(selected ? 1 : 0.5f);
- }
- }
-
- ImageButton getTabButton() {
- return mTabButton;
- }
-
- void onCreateTabButton(final LayoutInflater inflater, final ViewGroup parent) {
- mTabButton = (ImageButton) inflater.inflate(
- R.layout.mediapicker_tab_button,
- parent,
- false /* addToParent */);
- mTabButton.setImageResource(getIconResource());
- mTabButton.setContentDescription(
- inflater.getContext().getResources().getString(getIconDescriptionResource()));
- setSelected(mSelected);
- mTabButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(final View view) {
- mMediaPicker.selectChooser(MediaChooser.this);
- }
- });
- }
-
- protected Context getContext() {
- return mMediaPicker.getActivity();
- }
-
- protected FragmentManager getFragmentManager() {
- return OsUtil.isAtLeastJB_MR1() ? mMediaPicker.getChildFragmentManager() :
- mMediaPicker.getFragmentManager();
- }
- protected LayoutInflater getLayoutInflater() {
- return LayoutInflater.from(getContext());
- }
-
- /** Allows the chooser to handle full screen change */
- void onFullScreenChanged(final boolean fullScreen) {}
-
- /** Allows the chooser to handle the chooser being opened or closed */
- void onOpenedChanged(final boolean open) {
- mOpen = open;
- }
-
- /** @return The bit field of media types that this chooser can pick */
- public abstract int getSupportedMediaTypes();
-
- /** @return The resource id of the icon for the chooser */
- abstract int getIconResource();
-
- /** @return The resource id of the string to use for the accessibility text of the icon */
- abstract int getIconDescriptionResource();
-
- /**
- * Sets up the action bar to show the current state of the full-screen chooser
- * @param actionBar The action bar to populate
- */
- void updateActionBar(final ActionBar actionBar) {
- final int actionBarTitleResId = getActionBarTitleResId();
- if (actionBarTitleResId == 0) {
- actionBar.hide();
- } else {
- actionBar.setCustomView(null);
- actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE);
- actionBar.setDisplayHomeAsUpEnabled(true);
- actionBar.show();
- // Use X instead of <- in the action bar
- actionBar.setHomeAsUpIndicator(R.drawable.ic_remove_small_light);
- actionBar.setTitle(actionBarTitleResId);
- }
- }
-
- /**
- * Returns the resource Id used for the action bar title.
- */
- abstract int getActionBarTitleResId();
-
- /**
- * Throws an exception if the media chooser object doesn't require data support.
- */
- public void onDataUpdated(final Object data, final int loaderId) {
- throw new IllegalStateException();
- }
-
- /**
- * Called by the MediaPicker to determine whether this panel can be swiped down further. If
- * not, then a swipe down gestured will be captured by the MediaPickerPanel to shrink the
- * entire panel.
- */
- public boolean canSwipeDown() {
- return false;
- }
-
- /**
- * Typically the media picker is closed when the IME is opened, but this allows the chooser to
- * specify that showing the IME is okay while the chooser is up
- */
- public boolean canShowIme() {
- return false;
- }
-
- public boolean onBackPressed() {
- return false;
- }
-
- public void onCreateOptionsMenu(final MenuInflater inflater, final Menu menu) {
- }
-
- public boolean onOptionsItemSelected(final MenuItem item) {
- return false;
- }
-
- public void setThemeColor(final int color) {
- }
-
- /**
- * Returns true if the chooser is owning any incoming touch events, so that the media picker
- * panel won't process it and slide the panel.
- */
- public boolean isHandlingTouch() {
- return false;
- }
-
- public void stopTouchHandling() {
- }
-
- @Override
- public int getConversationSelfSubId() {
- return mMediaPicker.getConversationSelfSubId();
- }
-
- /** Optional activity life-cycle methods to be overridden by subclasses */
- public void onPause() { }
- public void onResume() { }
- protected void onRequestPermissionsResult(
- final int requestCode, final String permissions[], final int[] grantResults) { }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/MediaPicker.java b/src/com/android/messaging/ui/mediapicker/MediaPicker.java
deleted file mode 100644
index f441d09..0000000
--- a/src/com/android/messaging/ui/mediapicker/MediaPicker.java
+++ /dev/null
@@ -1,736 +0,0 @@
-/*
- * 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.mediapicker;
-
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v4.view.PagerAdapter;
-import android.support.v4.view.ViewPager;
-import android.support.v7.app.ActionBar;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-
-import com.android.messaging.Factory;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.DataModel;
-import com.android.messaging.datamodel.binding.Binding;
-import com.android.messaging.datamodel.binding.BindingBase;
-import com.android.messaging.datamodel.binding.ImmutableBindingRef;
-import com.android.messaging.datamodel.data.DraftMessageData;
-import com.android.messaging.datamodel.data.MediaPickerData;
-import com.android.messaging.datamodel.data.MessagePartData;
-import com.android.messaging.datamodel.data.PendingAttachmentData;
-import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
-import com.android.messaging.ui.BugleActionBarActivity;
-import com.android.messaging.ui.FixedViewPagerAdapter;
-import com.android.messaging.ui.mediapicker.DocumentImagePicker.SelectionListener;
-import com.android.messaging.util.AccessibilityUtil;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.UiUtils;
-import com.google.common.annotations.VisibleForTesting;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Fragment used to select or capture media to be added to the message
- */
-public class MediaPicker extends Fragment implements DraftMessageSubscriptionDataProvider {
- /** The listener interface for events from the media picker */
- public interface MediaPickerListener {
- /** Called when the media picker is opened so the host can accommodate the UI */
- void onOpened();
-
- /**
- * Called when the media picker goes into or leaves full screen mode so the host can
- * accommodate the fullscreen UI
- */
- void onFullScreenChanged(boolean fullScreen);
-
- /**
- * Called when the user selects one or more items
- * @param items The list of items which were selected
- */
- void onItemsSelected(Collection<MessagePartData> items, boolean dismissMediaPicker);
-
- /**
- * Called when the user unselects one item.
- */
- void onItemUnselected(MessagePartData item);
-
- /**
- * Called when the media picker is closed. Always called immediately after onItemsSelected
- */
- void onDismissed();
-
- /**
- * Called when media item selection is confirmed in a multi-select action.
- */
- void onConfirmItemSelection();
-
- /**
- * Called when a pending attachment is added.
- * @param pendingItem the pending attachment data being loaded.
- */
- void onPendingItemAdded(PendingAttachmentData pendingItem);
-
- /**
- * Called when a new media chooser is selected.
- */
- void onChooserSelected(final int chooserIndex);
- }
-
- /** The tag used when registering and finding this fragment */
- public static final String FRAGMENT_TAG = "mediapicker";
-
- // Media type constants that the media picker supports
- public static final int MEDIA_TYPE_DEFAULT = 0x0000;
- public static final int MEDIA_TYPE_NONE = 0x0000;
- public static final int MEDIA_TYPE_IMAGE = 0x0001;
- public static final int MEDIA_TYPE_VIDEO = 0x0002;
- public static final int MEDIA_TYPE_AUDIO = 0x0004;
- public static final int MEDIA_TYPE_VCARD = 0x0008;
- public static final int MEDIA_TYPE_LOCATION = 0x0010;
- private static final int MEDA_TYPE_INVALID = 0x0020;
- public static final int MEDIA_TYPE_ALL = 0xFFFF;
-
- /** The listener to call when events occur */
- private MediaPickerListener mListener;
-
- /** The handler used to dispatch calls to the listener */
- private Handler mListenerHandler;
-
- /** The bit flags of media types supported */
- private int mSupportedMediaTypes;
-
- /** The list of choosers which could be within the media picker */
- private final MediaChooser[] mChoosers;
-
- /** The list of currently enabled choosers */
- private final ArrayList<MediaChooser> mEnabledChoosers;
-
- /** The currently selected chooser */
- private MediaChooser mSelectedChooser;
-
- /** The main panel that controls the custom layout */
- private MediaPickerPanel mMediaPickerPanel;
-
- /** The linear layout that holds the icons to select individual chooser tabs */
- private LinearLayout mTabStrip;
-
- /** The view pager to swap between choosers */
- private ViewPager mViewPager;
-
- /** The current pager adapter for the view pager */
- private FixedViewPagerAdapter<MediaChooser> mPagerAdapter;
-
- /** True if the media picker is visible */
- private boolean mOpen;
-
- /** The theme color to use to make the media picker match the rest of the UI */
- private int mThemeColor;
-
- @VisibleForTesting
- final Binding<MediaPickerData> mBinding = BindingBase.createBinding(this);
-
- /** Handles picking image from the document picker */
- private DocumentImagePicker mDocumentImagePicker;
-
- /** Provides subscription-related data to access per-subscription configurations. */
- private DraftMessageSubscriptionDataProvider mSubscriptionDataProvider;
-
- /** Provides access to DraftMessageData associated with the current conversation */
- private ImmutableBindingRef<DraftMessageData> mDraftMessageDataModel;
-
- public MediaPicker() {
- this(Factory.get().getApplicationContext());
- }
-
- public MediaPicker(final Context context) {
- mBinding.bind(DataModel.get().createMediaPickerData(context));
- mEnabledChoosers = new ArrayList<MediaChooser>();
- mChoosers = new MediaChooser[] {
- new CameraMediaChooser(this),
- new GalleryMediaChooser(this),
- new AudioMediaChooser(this),
- };
-
- mOpen = false;
- setSupportedMediaTypes(MEDIA_TYPE_ALL);
- }
-
- private boolean mIsAttached;
- private int mStartingMediaTypeOnAttach = MEDA_TYPE_INVALID;
- private boolean mAnimateOnAttach;
-
- @Override
- public void onAttach (final Activity activity) {
- super.onAttach(activity);
- mIsAttached = true;
- if (mStartingMediaTypeOnAttach != MEDA_TYPE_INVALID) {
- // open() was previously called. Do the pending open now.
- doOpen(mStartingMediaTypeOnAttach, mAnimateOnAttach);
- }
- }
-
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mBinding.getData().init(getLoaderManager());
- mDocumentImagePicker = new DocumentImagePicker(this,
- new SelectionListener() {
- @Override
- public void onDocumentSelected(final PendingAttachmentData data) {
- if (mBinding.isBound()) {
- dispatchPendingItemAdded(data);
- }
- }
- });
- }
-
- @Override
- public View onCreateView(
- final LayoutInflater inflater,
- final ViewGroup container,
- final Bundle savedInstanceState) {
- mMediaPickerPanel = (MediaPickerPanel) inflater.inflate(
- R.layout.mediapicker_fragment,
- container,
- false);
- mMediaPickerPanel.setMediaPicker(this);
- mTabStrip = (LinearLayout) mMediaPickerPanel.findViewById(R.id.mediapicker_tabstrip);
- mTabStrip.setBackgroundColor(mThemeColor);
- for (final MediaChooser chooser : mChoosers) {
- chooser.onCreateTabButton(inflater, mTabStrip);
- final boolean enabled = (chooser.getSupportedMediaTypes() & mSupportedMediaTypes) !=
- MEDIA_TYPE_NONE;
- final ImageButton tabButton = chooser.getTabButton();
- if (tabButton != null) {
- tabButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
- mTabStrip.addView(tabButton);
- }
- }
-
- mViewPager = (ViewPager) mMediaPickerPanel.findViewById(R.id.mediapicker_view_pager);
- mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
- @Override
- public void onPageScrolled(
- final int position,
- final float positionOffset,
- final int positionOffsetPixels) {
- }
-
- @Override
- public void onPageSelected(int position) {
- // The position returned is relative to if we are in RtL mode. This class never
- // switches the indices of the elements if we are in RtL mode so we need to
- // translate the index back. For example, if the user clicked the item most to the
- // right in RtL mode we would want the index to appear as 0 here, however the
- // position returned would the last possible index.
- if (UiUtils.isRtlMode()) {
- position = mEnabledChoosers.size() - 1 - position;
- }
- selectChooser(mEnabledChoosers.get(position));
- }
-
- @Override
- public void onPageScrollStateChanged(final int state) {
- }
- });
- // Camera initialization is expensive, so don't realize offscreen pages if not needed.
- mViewPager.setOffscreenPageLimit(0);
- mViewPager.setAdapter(mPagerAdapter);
- final boolean isTouchExplorationEnabled = AccessibilityUtil.isTouchExplorationEnabled(
- getActivity());
- mMediaPickerPanel.setFullScreenOnly(isTouchExplorationEnabled);
- mMediaPickerPanel.setExpanded(mOpen, true, mEnabledChoosers.indexOf(mSelectedChooser));
- return mMediaPickerPanel;
- }
-
- @Override
- public void onPause() {
- super.onPause();
- CameraManager.get().onPause();
- for (final MediaChooser chooser : mEnabledChoosers) {
- chooser.onPause();
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
- CameraManager.get().onResume();
-
- for (final MediaChooser chooser : mEnabledChoosers) {
- chooser.onResume();
- }
- }
-
- @Override
- public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- mDocumentImagePicker.onActivityResult(requestCode, resultCode, data);
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- mBinding.unbind();
- }
-
- /**
- * Sets the theme color to make the media picker match the surrounding UI
- * @param themeColor The new theme color
- */
- public void setConversationThemeColor(final int themeColor) {
- mThemeColor = themeColor;
- if (mTabStrip != null) {
- mTabStrip.setBackgroundColor(mThemeColor);
- }
-
- for (final MediaChooser chooser : mEnabledChoosers) {
- chooser.setThemeColor(mThemeColor);
- }
- }
-
- /**
- * Gets the current conversation theme color.
- */
- public int getConversationThemeColor() {
- return mThemeColor;
- }
-
- public void setDraftMessageDataModel(final BindingBase<DraftMessageData> draftBinding) {
- mDraftMessageDataModel = Binding.createBindingReference(draftBinding);
- }
-
- public ImmutableBindingRef<DraftMessageData> getDraftMessageDataModel() {
- return mDraftMessageDataModel;
- }
-
- public void setSubscriptionDataProvider(final DraftMessageSubscriptionDataProvider provider) {
- mSubscriptionDataProvider = provider;
- }
-
- @Override
- public int getConversationSelfSubId() {
- return mSubscriptionDataProvider.getConversationSelfSubId();
- }
-
- /**
- * Opens the media picker and optionally shows the chooser for the supplied media type
- * @param startingMediaType The media type of the chooser to open if {@link #MEDIA_TYPE_DEFAULT}
- * is used, then the default chooser from saved shared prefs is opened
- */
- public void open(final int startingMediaType, final boolean animate) {
- mOpen = true;
- if (mIsAttached) {
- doOpen(startingMediaType, animate);
- } else {
- // open() can get called immediately after the MediaPicker is created. In that case,
- // we defer doing work as it may require an attached fragment (eg. calling
- // Fragment#requestPermission)
- mStartingMediaTypeOnAttach = startingMediaType;
- mAnimateOnAttach = animate;
- }
- }
-
- private void doOpen(int startingMediaType, final boolean animate) {
- final boolean isTouchExplorationEnabled = AccessibilityUtil.isTouchExplorationEnabled(
- // getActivity() will be null at this point
- Factory.get().getApplicationContext());
-
- // If no specific starting type is specified (i.e. MEDIA_TYPE_DEFAULT), try to get the
- // last opened chooser index from shared prefs.
- if (startingMediaType == MEDIA_TYPE_DEFAULT) {
- final int selectedChooserIndex = mBinding.getData().getSelectedChooserIndex();
- if (selectedChooserIndex >= 0 && selectedChooserIndex < mEnabledChoosers.size()) {
- selectChooser(mEnabledChoosers.get(selectedChooserIndex));
- } else {
- // This is the first time the picker is being used
- if (isTouchExplorationEnabled) {
- // Accessibility defaults to audio attachment mode.
- startingMediaType = MEDIA_TYPE_AUDIO;
- }
- }
- }
-
- if (mSelectedChooser == null) {
- for (final MediaChooser chooser : mEnabledChoosers) {
- if (startingMediaType == MEDIA_TYPE_DEFAULT ||
- (startingMediaType & chooser.getSupportedMediaTypes()) != MEDIA_TYPE_NONE) {
- selectChooser(chooser);
- break;
- }
- }
- }
-
- if (mSelectedChooser == null) {
- // Fall back to the first chooser.
- selectChooser(mEnabledChoosers.get(0));
- }
-
- if (mMediaPickerPanel != null) {
- mMediaPickerPanel.setFullScreenOnly(isTouchExplorationEnabled);
- mMediaPickerPanel.setExpanded(true, animate,
- mEnabledChoosers.indexOf(mSelectedChooser));
- }
- }
-
- /** @return True if the media picker is open */
- public boolean isOpen() {
- return mOpen;
- }
-
- /**
- * Sets the list of media types to allow the user to select
- * @param mediaTypes The bit flags of media types to allow. Can be any combination of the
- * MEDIA_TYPE_* values
- */
- void setSupportedMediaTypes(final int mediaTypes) {
- mSupportedMediaTypes = mediaTypes;
- mEnabledChoosers.clear();
- boolean selectNextChooser = false;
- for (final MediaChooser chooser : mChoosers) {
- final boolean enabled = (chooser.getSupportedMediaTypes() & mSupportedMediaTypes) !=
- MEDIA_TYPE_NONE;
- if (enabled) {
- // TODO Add a way to inform the chooser which media types are supported
- mEnabledChoosers.add(chooser);
- if (selectNextChooser) {
- selectChooser(chooser);
- selectNextChooser = false;
- }
- } else if (mSelectedChooser == chooser) {
- selectNextChooser = true;
- }
- final ImageButton tabButton = chooser.getTabButton();
- if (tabButton != null) {
- tabButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
- }
- }
-
- if (selectNextChooser && mEnabledChoosers.size() > 0) {
- selectChooser(mEnabledChoosers.get(0));
- }
- final MediaChooser[] enabledChoosers = new MediaChooser[mEnabledChoosers.size()];
- mEnabledChoosers.toArray(enabledChoosers);
- mPagerAdapter = new FixedViewPagerAdapter<MediaChooser>(enabledChoosers);
- if (mViewPager != null) {
- mViewPager.setAdapter(mPagerAdapter);
- }
-
- // Only rebind data if we are currently bound. Otherwise, we must have not
- // bound to any data yet and should wait until onCreate() to bind data.
- if (mBinding.isBound() && getActivity() != null) {
- mBinding.unbind();
- mBinding.bind(DataModel.get().createMediaPickerData(getActivity()));
- mBinding.getData().init(getLoaderManager());
- }
- }
-
- ViewPager getViewPager() {
- return mViewPager;
- }
-
- /** Hides the media picker, and frees up any resources it’s using */
- public void dismiss(final boolean animate) {
- mOpen = false;
- if (mMediaPickerPanel != null) {
- mMediaPickerPanel.setExpanded(false, animate, MediaPickerPanel.PAGE_NOT_SET);
- }
- mSelectedChooser = null;
- }
-
- /**
- * Sets the listener for the media picker events
- * @param listener The listener which will receive events
- */
- public void setListener(final MediaPickerListener listener) {
- Assert.isMainThread();
- mListener = listener;
- mListenerHandler = listener != null ? new Handler() : null;
- }
-
- /** @return True if the media picker is in full-screen mode */
- public boolean isFullScreen() {
- return mMediaPickerPanel != null && mMediaPickerPanel.isFullScreen();
- }
-
- public void setFullScreen(final boolean fullScreen) {
- mMediaPickerPanel.setFullScreenView(fullScreen, true);
- }
-
- public void updateActionBar(final ActionBar actionBar) {
- if (getActivity() == null) {
- return;
- }
- if (isFullScreen() && mSelectedChooser != null) {
- mSelectedChooser.updateActionBar(actionBar);
- } else {
- actionBar.hide();
- }
- }
-
- /**
- * Selects a new chooser
- * @param newSelectedChooser The newly selected chooser
- */
- void selectChooser(final MediaChooser newSelectedChooser) {
- if (mSelectedChooser == newSelectedChooser) {
- return;
- }
-
- if (mSelectedChooser != null) {
- mSelectedChooser.setSelected(false);
- }
- mSelectedChooser = newSelectedChooser;
- if (mSelectedChooser != null) {
- mSelectedChooser.setSelected(true);
- }
-
- final int chooserIndex = mEnabledChoosers.indexOf(mSelectedChooser);
- if (mViewPager != null) {
- mViewPager.setCurrentItem(chooserIndex, true /* smoothScroll */);
- }
-
- if (isFullScreen()) {
- invalidateOptionsMenu();
- }
-
- // Save the newly selected chooser's index so we may directly switch to it the
- // next time user opens the media picker.
- mBinding.getData().saveSelectedChooserIndex(chooserIndex);
- if (mMediaPickerPanel != null) {
- mMediaPickerPanel.onChooserChanged();
- }
- dispatchChooserSelected(chooserIndex);
- }
-
- public boolean canShowIme() {
- if (mSelectedChooser != null) {
- return mSelectedChooser.canShowIme();
- }
- return false;
- }
-
- public boolean onBackPressed() {
- return mSelectedChooser != null && mSelectedChooser.onBackPressed();
- }
-
- void invalidateOptionsMenu() {
- ((BugleActionBarActivity) getActivity()).supportInvalidateOptionsMenu();
- }
-
- void dispatchOpened() {
- setHasOptionsMenu(false);
- mOpen = true;
- mPagerAdapter.notifyDataSetChanged();
- if (mListener != null) {
- mListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onOpened();
- }
- });
- }
- if (mSelectedChooser != null) {
- mSelectedChooser.onFullScreenChanged(false);
- mSelectedChooser.onOpenedChanged(true);
- }
- }
-
- void dispatchDismissed() {
- setHasOptionsMenu(false);
- mOpen = false;
- if (mListener != null) {
- mListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onDismissed();
- }
- });
- }
- if (mSelectedChooser != null) {
- mSelectedChooser.onOpenedChanged(false);
- }
- }
-
- void dispatchFullScreen(final boolean fullScreen) {
- setHasOptionsMenu(fullScreen);
- if (mListener != null) {
- mListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onFullScreenChanged(fullScreen);
- }
- });
- }
- if (mSelectedChooser != null) {
- mSelectedChooser.onFullScreenChanged(fullScreen);
- }
- }
-
- void dispatchItemsSelected(final MessagePartData item, final boolean dismissMediaPicker) {
- final List<MessagePartData> items = new ArrayList<MessagePartData>(1);
- items.add(item);
- dispatchItemsSelected(items, dismissMediaPicker);
- }
-
- void dispatchItemsSelected(final Collection<MessagePartData> items,
- final boolean dismissMediaPicker) {
- if (mListener != null) {
- mListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onItemsSelected(items, dismissMediaPicker);
- }
- });
- }
-
- if (isFullScreen() && !dismissMediaPicker) {
- invalidateOptionsMenu();
- }
- }
-
- void dispatchItemUnselected(final MessagePartData item) {
- if (mListener != null) {
- mListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onItemUnselected(item);
- }
- });
- }
-
- if (isFullScreen()) {
- invalidateOptionsMenu();
- }
- }
-
- void dispatchConfirmItemSelection() {
- if (mListener != null) {
- mListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onConfirmItemSelection();
- }
- });
- }
- }
-
- void dispatchPendingItemAdded(final PendingAttachmentData pendingItem) {
- if (mListener != null) {
- mListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onPendingItemAdded(pendingItem);
- }
- });
- }
-
- if (isFullScreen()) {
- invalidateOptionsMenu();
- }
- }
-
- void dispatchChooserSelected(final int chooserIndex) {
- if (mListener != null) {
- mListenerHandler.post(new Runnable() {
- @Override
- public void run() {
- mListener.onChooserSelected(chooserIndex);
- }
- });
- }
- }
-
- public boolean canSwipeDownChooser() {
- return mSelectedChooser == null ? false : mSelectedChooser.canSwipeDown();
- }
-
- public boolean isChooserHandlingTouch() {
- return mSelectedChooser == null ? false : mSelectedChooser.isHandlingTouch();
- }
-
- public void stopChooserTouchHandling() {
- if (mSelectedChooser != null) {
- mSelectedChooser.stopTouchHandling();
- }
- }
-
- boolean getChooserShowsActionBarInFullScreen() {
- return mSelectedChooser == null ? false : mSelectedChooser.getActionBarTitleResId() != 0;
- }
-
- @Override
- public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
- if (mSelectedChooser != null) {
- mSelectedChooser.onCreateOptionsMenu(inflater, menu);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- return (mSelectedChooser != null && mSelectedChooser.onOptionsItemSelected(item)) ||
- super.onOptionsItemSelected(item);
- }
-
- PagerAdapter getPagerAdapter() {
- return mPagerAdapter;
- }
-
- public void resetViewHolderState() {
- mPagerAdapter.resetState();
- }
-
- /**
- * Launch an external picker to pick item from document picker as attachment.
- */
- public void launchDocumentPicker() {
- mDocumentImagePicker.launchPicker();
- }
-
- public ImmutableBindingRef<MediaPickerData> getMediaPickerDataBinding() {
- return BindingBase.createBindingReference(mBinding);
- }
-
- protected static final int CAMERA_PERMISSION_REQUEST_CODE = 1;
- protected static final int LOCATION_PERMISSION_REQUEST_CODE = 2;
- protected static final int RECORD_AUDIO_PERMISSION_REQUEST_CODE = 3;
- protected static final int GALLERY_PERMISSION_REQUEST_CODE = 4;
-
- @Override
- public void onRequestPermissionsResult(
- final int requestCode, final String permissions[], final int[] grantResults) {
- if (mSelectedChooser != null) {
- mSelectedChooser.onRequestPermissionsResult(requestCode, permissions, grantResults);
- }
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/MediaPickerGridView.java b/src/com/android/messaging/ui/mediapicker/MediaPickerGridView.java
deleted file mode 100644
index cc3a4a1..0000000
--- a/src/com/android/messaging/ui/mediapicker/MediaPickerGridView.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.GridView;
-
-public class MediaPickerGridView extends GridView {
-
- public MediaPickerGridView(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- /**
- * Returns if the grid view can be swiped down further. It cannot be swiped down
- * if there's no item or if we are already at the top.
- */
- public boolean canSwipeDown() {
- if (getAdapter() == null || getAdapter().getCount() == 0 || getChildCount() == 0) {
- return false;
- }
-
- final int firstVisiblePosition = getFirstVisiblePosition();
- if (firstVisiblePosition == 0 && getChildAt(0).getTop() >= 0) {
- return false;
- }
- return true;
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/MediaPickerPanel.java b/src/com/android/messaging/ui/mediapicker/MediaPickerPanel.java
deleted file mode 100644
index 56b0a03..0000000
--- a/src/com/android/messaging/ui/mediapicker/MediaPickerPanel.java
+++ /dev/null
@@ -1,563 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.os.Handler;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.animation.Animation;
-import android.view.animation.Transformation;
-import android.widget.LinearLayout;
-
-import com.android.messaging.R;
-import com.android.messaging.ui.PagingAwareViewPager;
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.OsUtil;
-import com.android.messaging.util.UiUtils;
-
-/**
- * Custom layout panel which makes the MediaPicker animations seamless and synchronized
- * Designed to be very specific to the MediaPicker's usage
- */
-public class MediaPickerPanel extends ViewGroup {
- /**
- * The window of time in which we might to decide to reinterpret the intent of a gesture.
- */
- private static final long TOUCH_RECAPTURE_WINDOW_MS = 500L;
-
- // The two view components to layout
- private LinearLayout mTabStrip;
- private boolean mFullScreenOnly;
- private PagingAwareViewPager mViewPager;
-
- /**
- * True if the MediaPicker is full screen or animating into it
- */
- private boolean mFullScreen;
-
- /**
- * True if the MediaPicker is open at all
- */
- private boolean mExpanded;
-
- /**
- * The current desired height of the MediaPicker. This property may be animated and the
- * measure pass uses it to determine what size the components are.
- */
- private int mCurrentDesiredHeight;
-
- private final Handler mHandler = new Handler();
-
- /**
- * The media picker for dispatching events to the MediaPicker's listener
- */
- private MediaPicker mMediaPicker;
-
- /**
- * The computed default "half-screen" height of the view pager in px
- */
- private final int mDefaultViewPagerHeight;
-
- /**
- * The action bar height used to compute the padding on the view pager when it's full screen.
- */
- private final int mActionBarHeight;
-
- private TouchHandler mTouchHandler;
-
- static final int PAGE_NOT_SET = -1;
-
- public MediaPickerPanel(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- // Cache the computed dimension
- mDefaultViewPagerHeight = getResources().getDimensionPixelSize(
- R.dimen.mediapicker_default_chooser_height);
- mActionBarHeight = getResources().getDimensionPixelSize(R.dimen.action_bar_height);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mTabStrip = (LinearLayout) findViewById(R.id.mediapicker_tabstrip);
- mViewPager = (PagingAwareViewPager) findViewById(R.id.mediapicker_view_pager);
- mTouchHandler = new TouchHandler();
- setOnTouchListener(mTouchHandler);
- mViewPager.setOnTouchListener(mTouchHandler);
-
- // Make sure full screen mode is updated in landscape mode change when the panel is open.
- addOnLayoutChangeListener(new OnLayoutChangeListener() {
- private boolean mLandscapeMode = UiUtils.isLandscapeMode();
-
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- final boolean newLandscapeMode = UiUtils.isLandscapeMode();
- if (mLandscapeMode != newLandscapeMode) {
- mLandscapeMode = newLandscapeMode;
- if (mExpanded) {
- setExpanded(mExpanded, false /* animate */, mViewPager.getCurrentItem(),
- true /* force */);
- }
- }
- }
- });
- }
-
- @Override
- protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
- int requestedHeight = MeasureSpec.getSize(heightMeasureSpec);
- if (mMediaPicker.getChooserShowsActionBarInFullScreen()) {
- requestedHeight -= mActionBarHeight;
- }
- int desiredHeight = Math.min(mCurrentDesiredHeight, requestedHeight);
- if (mExpanded && desiredHeight == 0) {
- // If we want to be shown, we have to have a non-0 height. Returning a height of 0 will
- // cause the framework to abort the animation from 0, so we must always have some
- // height once we start expanding
- desiredHeight = 1;
- } else if (!mExpanded && desiredHeight == 0) {
- mViewPager.setVisibility(View.GONE);
- mViewPager.setAdapter(null);
- }
-
- measureChild(mTabStrip, widthMeasureSpec, heightMeasureSpec);
-
- int tabStripHeight;
- if (requiresFullScreen()) {
- // Ensure that the tab strip is always visible, even in full screen.
- tabStripHeight = mTabStrip.getMeasuredHeight();
- } else {
- // Slide out the tab strip at the end of the animation to full screen.
- tabStripHeight = Math.min(mTabStrip.getMeasuredHeight(),
- requestedHeight - desiredHeight);
- }
-
- // If we are animating and have an interim desired height, use the default height. We can't
- // take the max here as on some devices the mDefaultViewPagerHeight may be too big in
- // landscape mode after animation.
- final int tabAdjustedDesiredHeight = desiredHeight - tabStripHeight;
- final int viewPagerHeight =
- tabAdjustedDesiredHeight <= 1 ? mDefaultViewPagerHeight : tabAdjustedDesiredHeight;
-
- int viewPagerHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
- viewPagerHeight, MeasureSpec.EXACTLY);
- measureChild(mViewPager, widthMeasureSpec, viewPagerHeightMeasureSpec);
- setMeasuredDimension(mViewPager.getMeasuredWidth(), desiredHeight);
- }
-
- @Override
- protected void onLayout(final boolean changed, final int left, final int top, final int right,
- final int bottom) {
- int y = top;
- final int width = right - left;
-
- final int viewPagerHeight = mViewPager.getMeasuredHeight();
- mViewPager.layout(0, y, width, y + viewPagerHeight);
- y += viewPagerHeight;
-
- mTabStrip.layout(0, y, width, y + mTabStrip.getMeasuredHeight());
- }
-
- void onChooserChanged() {
- if (mFullScreen) {
- setDesiredHeight(getDesiredHeight(), true);
- }
- }
-
- void setFullScreenOnly(boolean fullScreenOnly) {
- mFullScreenOnly = fullScreenOnly;
- }
-
- boolean isFullScreen() {
- return mFullScreen;
- }
-
- void setMediaPicker(final MediaPicker mediaPicker) {
- mMediaPicker = mediaPicker;
- }
-
- /**
- * Get the desired height of the media picker panel for when the panel is not in motion (i.e.
- * not being dragged by the user).
- */
- private int getDesiredHeight() {
- if (mFullScreen) {
- int fullHeight = getContext().getResources().getDisplayMetrics().heightPixels;
- if (OsUtil.isAtLeastKLP() && isAttachedToWindow()) {
- // When we're attached to the window, we can get an accurate height, not necessary
- // on older API level devices because they don't include the action bar height
- View composeContainer =
- getRootView().findViewById(R.id.conversation_and_compose_container);
- if (composeContainer != null) {
- // protect against composeContainer having been unloaded already
- fullHeight -= UiUtils.getMeasuredBoundsOnScreen(composeContainer).top;
- }
- }
- if (mMediaPicker.getChooserShowsActionBarInFullScreen()) {
- return fullHeight - mActionBarHeight;
- } else {
- return fullHeight;
- }
- } else if (mExpanded) {
- return LayoutParams.WRAP_CONTENT;
- } else {
- return 0;
- }
- }
-
- private void setupViewPager(final int startingPage) {
- mViewPager.setVisibility(View.VISIBLE);
- if (startingPage >= 0 && startingPage < mMediaPicker.getPagerAdapter().getCount()) {
- mViewPager.setAdapter(mMediaPicker.getPagerAdapter());
- mViewPager.setCurrentItem(startingPage);
- }
- updateViewPager();
- }
-
- /**
- * Expand the media picker panel. Since we always set the pager adapter to null when the panel
- * is collapsed, we need to restore the adapter and the starting page.
- * @param expanded expanded or collapsed
- * @param animate need animation
- * @param startingPage the desired selected page to start
- */
- void setExpanded(final boolean expanded, final boolean animate, final int startingPage) {
- setExpanded(expanded, animate, startingPage, false /* force */);
- }
-
- private void setExpanded(final boolean expanded, final boolean animate, final int startingPage,
- final boolean force) {
- if (expanded == mExpanded && !force) {
- return;
- }
- mFullScreen = false;
- mExpanded = expanded;
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- setDesiredHeight(getDesiredHeight(), animate);
- }
- });
- if (expanded) {
- setupViewPager(startingPage);
- mMediaPicker.dispatchOpened();
- } else {
- mMediaPicker.dispatchDismissed();
- }
-
- // Call setFullScreenView() when we are in landscape mode so it can go full screen as
- // soon as it is expanded.
- if (expanded && requiresFullScreen()) {
- setFullScreenView(true, animate);
- }
- }
-
- private boolean requiresFullScreen() {
- return mFullScreenOnly || UiUtils.isLandscapeMode();
- }
-
- private void setDesiredHeight(int height, final boolean animate) {
- final int startHeight = mCurrentDesiredHeight;
- if (height == LayoutParams.WRAP_CONTENT) {
- height = measureHeight();
- }
- clearAnimation();
- if (animate) {
- final int deltaHeight = height - startHeight;
- final Animation animation = new Animation() {
- @Override
- protected void applyTransformation(final float interpolatedTime,
- final Transformation t) {
- mCurrentDesiredHeight = (int) (startHeight + deltaHeight * interpolatedTime);
- requestLayout();
- }
-
- @Override
- public boolean willChangeBounds() {
- return true;
- }
- };
- animation.setDuration(UiUtils.MEDIAPICKER_TRANSITION_DURATION);
- animation.setInterpolator(UiUtils.EASE_OUT_INTERPOLATOR);
- startAnimation(animation);
- } else {
- mCurrentDesiredHeight = height;
- }
- requestLayout();
- }
-
- /**
- * @return The minimum total height of the view
- */
- private int measureHeight() {
- final int measureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE, MeasureSpec.AT_MOST);
- measureChild(mTabStrip, measureSpec, measureSpec);
- return mDefaultViewPagerHeight + mTabStrip.getMeasuredHeight();
- }
-
- /**
- * Enters or leaves full screen view
- *
- * @param fullScreen True to enter full screen view, false to leave
- * @param animate True to animate the transition
- */
- void setFullScreenView(final boolean fullScreen, final boolean animate) {
- if (fullScreen == mFullScreen) {
- return;
- }
-
- if (requiresFullScreen() && !fullScreen) {
- setExpanded(false /* expanded */, true /* animate */, PAGE_NOT_SET);
- return;
- }
- mFullScreen = fullScreen;
- setDesiredHeight(getDesiredHeight(), animate);
- mMediaPicker.dispatchFullScreen(mFullScreen);
- updateViewPager();
- }
-
- /**
- * ViewPager should have its paging disabled when in full screen mode.
- */
- private void updateViewPager() {
- mViewPager.setPagingEnabled(!mFullScreen);
- }
-
- @Override
- public boolean onInterceptTouchEvent(final MotionEvent ev) {
- return mTouchHandler.onInterceptTouchEvent(ev) || super.onInterceptTouchEvent(ev);
- }
-
- /**
- * Helper class to handle touch events and swipe gestures
- */
- private class TouchHandler implements OnTouchListener {
- /**
- * The height of the view when the touch press started
- */
- private int mDownHeight = -1;
-
- /**
- * True if the panel moved at all (changed height) during the drag
- */
- private boolean mMoved = false;
-
- // The threshold constants converted from DP to px
- private final float mFlingThresholdPx;
- private final float mBigFlingThresholdPx;
-
- // The system defined pixel size to determine when a movement is considered a drag.
- private final int mTouchSlop;
-
- /**
- * A copy of the MotionEvent that started the drag/swipe gesture
- */
- private MotionEvent mDownEvent;
-
- /**
- * Whether we are currently moving down. We may not be able to move down in full screen
- * mode when the child view can swipe down (such as a list view).
- */
- private boolean mMovedDown = false;
-
- /**
- * Indicates whether the child view contained in the panel can swipe down at the beginning
- * of the drag event (i.e. the initial down). The MediaPanel can contain
- * scrollable children such as a list view / grid view. If the child view can swipe down,
- * We want to let the child view handle the scroll first instead of handling it ourselves.
- */
- private boolean mCanChildViewSwipeDown = false;
-
- /**
- * Necessary direction ratio for a fling to be considered in one direction this prevents
- * horizontal swipes with small vertical components from triggering vertical swipe actions
- */
- private static final float DIRECTION_RATIO = 1.1f;
-
- TouchHandler() {
- final Resources resources = getContext().getResources();
- final ViewConfiguration configuration = ViewConfiguration.get(getContext());
- mFlingThresholdPx = resources.getDimensionPixelSize(
- R.dimen.mediapicker_fling_threshold);
- mBigFlingThresholdPx = resources.getDimensionPixelSize(
- R.dimen.mediapicker_big_fling_threshold);
- mTouchSlop = configuration.getScaledTouchSlop();
- }
-
- /**
- * The media picker panel may contain scrollable children such as a GridView, which eats
- * all touch events before we get to it. Therefore, we'd like to intercept these events
- * before the children to determine if we should handle swiping down in full screen mode.
- * In non-full screen mode, we should handle all vertical scrolling events and leave
- * horizontal scrolling to the view pager.
- */
- public boolean onInterceptTouchEvent(final MotionEvent ev) {
- switch (ev.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- // Never capture the initial down, so that the children may handle it
- // as well. Let the touch handler know about the down event as well.
- mTouchHandler.onTouch(MediaPickerPanel.this, ev);
-
- // Ask the MediaPicker whether the contained view can be swiped down.
- // We record the value at the start of the drag to decide the swiping mode
- // for the entire motion.
- mCanChildViewSwipeDown = mMediaPicker.canSwipeDownChooser();
- return false;
-
- case MotionEvent.ACTION_MOVE: {
- if (mMediaPicker.isChooserHandlingTouch()) {
- if (shouldAllowRecaptureTouch(ev)) {
- mMediaPicker.stopChooserTouchHandling();
- mViewPager.setPagingEnabled(true);
- return false;
- }
- // If the chooser is claiming ownership on all touch events, then we
- // shouldn't try to handle them (neither should the view pager).
- mViewPager.setPagingEnabled(false);
- return false;
- } else if (mCanChildViewSwipeDown) {
- // Never capture event if the child view can swipe down.
- return false;
- } else if (!mFullScreen && mMoved) {
- // When we are not fullscreen, we own any vertical drag motion.
- return true;
- } else if (mMovedDown) {
- // We are currently handling the down swipe ourselves, so always
- // capture this event.
- return true;
- } else {
- // The current interaction mode is undetermined, so always let the
- // touch handler know about this event. However, don't capture this
- // event so the child may handle it as well.
- mTouchHandler.onTouch(MediaPickerPanel.this, ev);
-
- // Capture the touch event from now on if we are handling the drag.
- return mFullScreen ? mMovedDown : mMoved;
- }
- }
- }
- return false;
- }
-
- /**
- * Determine whether we think the user is actually trying to expand or slide despite the
- * fact that they touched first on a chooser that captured the input.
- */
- private boolean shouldAllowRecaptureTouch(MotionEvent ev) {
- final long elapsedMs = ev.getEventTime() - ev.getDownTime();
- if (mDownEvent == null || elapsedMs == 0 || elapsedMs > TOUCH_RECAPTURE_WINDOW_MS) {
- // Either we don't have info to decide or it's been long enough that we no longer
- // want to reinterpret user intent.
- return false;
- }
- final float dx = ev.getRawX() - mDownEvent.getRawX();
- final float dy = ev.getRawY() - mDownEvent.getRawY();
- final float dt = elapsedMs / 1000.0f;
- final float maxAbsDelta = Math.max(Math.abs(dx), Math.abs(dy));
- final float velocity = maxAbsDelta / dt;
- return velocity > mFlingThresholdPx;
- }
-
- @Override
- public boolean onTouch(final View view, final MotionEvent motionEvent) {
- switch (motionEvent.getAction()) {
- case MotionEvent.ACTION_UP: {
- if (!mMoved || mDownEvent == null) {
- return false;
- }
- final float dx = motionEvent.getRawX() - mDownEvent.getRawX();
- final float dy = motionEvent.getRawY() - mDownEvent.getRawY();
-
- final float dt =
- (motionEvent.getEventTime() - mDownEvent.getEventTime()) / 1000.0f;
- final float yVelocity = dy / dt;
-
- boolean handled = false;
-
- // Vertical swipe occurred if the direction is as least mostly in the y
- // component and has the required velocity (px/sec)
- if ((dx == 0 || (Math.abs(dy) / Math.abs(dx)) > DIRECTION_RATIO) &&
- Math.abs(yVelocity) > mFlingThresholdPx) {
- if (yVelocity < 0 && mExpanded) {
- setFullScreenView(true, true);
- handled = true;
- } else if (yVelocity > 0) {
- if (mFullScreen && yVelocity < mBigFlingThresholdPx) {
- setFullScreenView(false, true);
- } else {
- setExpanded(false, true, PAGE_NOT_SET);
- }
- handled = true;
- }
- }
-
- if (!handled) {
- // If they didn't swipe enough, animate back to resting state
- setDesiredHeight(getDesiredHeight(), true);
- }
- resetState();
- break;
- }
- case MotionEvent.ACTION_DOWN: {
- mDownHeight = getHeight();
- mDownEvent = MotionEvent.obtain(motionEvent);
- // If we are here and care about the return value (i.e. this is not called
- // from onInterceptTouchEvent), then presumably no children view in the panel
- // handles the down event. We'd like to handle future ACTION_MOVE events, so
- // always claim ownership on this event so it doesn't fall through and gets
- // cancelled by the framework.
- return true;
- }
- case MotionEvent.ACTION_MOVE: {
- if (mDownEvent == null) {
- return mMoved;
- }
-
- final float dx = mDownEvent.getRawX() - motionEvent.getRawX();
- final float dy = mDownEvent.getRawY() - motionEvent.getRawY();
- // Don't act if the move is mostly horizontal
- if (Math.abs(dy) > mTouchSlop &&
- (Math.abs(dy) / Math.abs(dx)) > DIRECTION_RATIO) {
- setDesiredHeight((int) (mDownHeight + dy), false);
- mMoved = true;
- if (dy < -mTouchSlop) {
- mMovedDown = true;
- }
- }
- return mMoved;
- }
-
- }
- return mMoved;
- }
-
- private void resetState() {
- mDownEvent = null;
- mDownHeight = -1;
- mMoved = false;
- mMovedDown = false;
- mCanChildViewSwipeDown = false;
- updateViewPager();
- }
- }
-}
-
diff --git a/src/com/android/messaging/ui/mediapicker/MmsVideoRecorder.java b/src/com/android/messaging/ui/mediapicker/MmsVideoRecorder.java
deleted file mode 100644
index 7ac7871..0000000
--- a/src/com/android/messaging/ui/mediapicker/MmsVideoRecorder.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.hardware.Camera;
-import android.media.CamcorderProfile;
-import android.media.MediaRecorder;
-import android.net.Uri;
-
-import com.android.messaging.Factory;
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.util.ContentType;
-import com.android.messaging.util.SafeAsyncTask;
-
-import java.io.FileNotFoundException;
-
-class MmsVideoRecorder extends MediaRecorder {
- private static final float VIDEO_OVERSHOOT_SLOP = .85F;
-
- private static final int BITS_PER_BYTE = 8;
-
- // We think user will expect to be able to record videos at least this long
- private static final long MIN_DURATION_LIMIT_SECONDS = 25;
-
- /** The uri where video is being recorded to */
- private Uri mTempVideoUri;
-
- /** The settings used for video recording */
- private final CamcorderProfile mCamcorderProfile;
-
- public MmsVideoRecorder(final Camera camera, final int cameraIndex, final int orientation,
- final int maxMessageSize)
- throws FileNotFoundException {
- mCamcorderProfile =
- CamcorderProfile.get(cameraIndex, CamcorderProfile.QUALITY_LOW);
- mTempVideoUri = MediaScratchFileProvider.buildMediaScratchSpaceUri(
- ContentType.getExtension(getContentType()));
-
- // The video recorder can sometimes return a file that's larger than the max we
- // say we can handle. Try to handle that overshoot by specifying an 85% limit.
- final long sizeLimit = (long) (maxMessageSize * VIDEO_OVERSHOOT_SLOP);
-
- // The QUALITY_LOW profile might not be low enough to allow for video of a reasonable
- // minimum duration. Adjust a/v bitrates to allow at least MIN_DURATION_LIMIT video
- // to be recorded.
- int audioBitRate = mCamcorderProfile.audioBitRate;
- int videoBitRate = mCamcorderProfile.videoBitRate;
- final double initialDurationLimit = sizeLimit * BITS_PER_BYTE
- / (double) (audioBitRate + videoBitRate);
- if (initialDurationLimit < MIN_DURATION_LIMIT_SECONDS) {
- // Reduce the suggested bitrates. These bitrates are only requests, if implementation
- // can't actually hit these goals it will still record video at higher rate and stop when
- // it hits the size limit.
- final double bitRateAdjustmentFactor = initialDurationLimit / MIN_DURATION_LIMIT_SECONDS;
- audioBitRate *= bitRateAdjustmentFactor;
- videoBitRate *= bitRateAdjustmentFactor;
- }
-
- setCamera(camera);
- setOrientationHint(orientation);
- setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
- setVideoSource(MediaRecorder.VideoSource.CAMERA);
- setOutputFormat(mCamcorderProfile.fileFormat);
- setOutputFile(
- Factory.get().getApplicationContext().getContentResolver().openFileDescriptor(
- mTempVideoUri, "w").getFileDescriptor());
-
- // Copy settings from CamcorderProfile to MediaRecorder
- setAudioEncodingBitRate(audioBitRate);
- setAudioChannels(mCamcorderProfile.audioChannels);
- setAudioEncoder(mCamcorderProfile.audioCodec);
- setAudioSamplingRate(mCamcorderProfile.audioSampleRate);
- setVideoEncodingBitRate(videoBitRate);
- setVideoEncoder(mCamcorderProfile.videoCodec);
- setVideoFrameRate(mCamcorderProfile.videoFrameRate);
- setVideoSize(
- mCamcorderProfile.videoFrameWidth, mCamcorderProfile.videoFrameHeight);
- setMaxFileSize(sizeLimit);
- }
-
- Uri getVideoUri() {
- return mTempVideoUri;
- }
-
- int getVideoWidth() {
- return mCamcorderProfile.videoFrameWidth;
- }
-
- int getVideoHeight() {
- return mCamcorderProfile.videoFrameHeight;
- }
-
- void cleanupTempFile() {
- final Uri tempUri = mTempVideoUri;
- SafeAsyncTask.executeOnThreadPool(new Runnable() {
- @Override
- public void run() {
- Factory.get().getApplicationContext().getContentResolver().delete(
- tempUri, null, null);
- }
- });
- mTempVideoUri = null;
- }
-
- String getContentType() {
- if (mCamcorderProfile.fileFormat == OutputFormat.MPEG_4) {
- return ContentType.VIDEO_MP4;
- } else {
- // 3GPP is the only other video format with a constant in OutputFormat
- return ContentType.VIDEO_3GPP;
- }
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/PausableChronometer.java b/src/com/android/messaging/ui/mediapicker/PausableChronometer.java
deleted file mode 100644
index dc8f90b..0000000
--- a/src/com/android/messaging/ui/mediapicker/PausableChronometer.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.content.Context;
-import android.os.SystemClock;
-import android.util.AttributeSet;
-import android.widget.Chronometer;
-
-import com.android.messaging.ui.PlaybackStateView;
-
-/**
- * A pausable Chronometer implementation. The default Chronometer in Android only stops the UI
- * from updating when you call stop(), but doesn't actually pause it. This implementation adds an
- * additional timestamp that tracks the timespan for the pause and compensate for that.
- */
-public class PausableChronometer extends Chronometer implements PlaybackStateView {
- // Keeps track of how far long the Chronometer has been tracking when it's paused. We'd like
- // to start from this time the next time it's resumed.
- private long mTimeWhenPaused = 0;
-
- public PausableChronometer(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- }
-
- /**
- * Reset the timer and start counting from zero.
- */
- @Override
- public void restart() {
- reset();
- start();
- }
-
- /**
- * Reset the timer to zero, but don't start it.
- */
- @Override
- public void reset() {
- stop();
- setBase(SystemClock.elapsedRealtime());
- mTimeWhenPaused = 0;
- }
-
- /**
- * Resume the timer after a previous pause.
- */
- @Override
- public void resume() {
- setBase(SystemClock.elapsedRealtime() - mTimeWhenPaused);
- start();
- }
-
- /**
- * Pause the timer.
- */
- @Override
- public void pause() {
- stop();
- mTimeWhenPaused = SystemClock.elapsedRealtime() - getBase();
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java b/src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java
deleted file mode 100644
index 5dc3185..0000000
--- a/src/com/android/messaging/ui/mediapicker/SoftwareCameraPreview.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.content.Context;
-import android.hardware.Camera;
-import android.os.Parcelable;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.View;
-
-import java.io.IOException;
-
-/**
- * A software rendered preview surface for the camera. This renders slower and causes more jank, so
- * HardwareCameraPreview is preferred if possible.
- *
- * There is a significant amount of duplication between HardwareCameraPreview and
- * SoftwareCameraPreview which we can't easily share due to a lack of multiple inheritance, The
- * implementations of the shared methods are delegated to CameraPreview
- */
-public class SoftwareCameraPreview extends SurfaceView implements CameraPreview.CameraPreviewHost {
- private final CameraPreview mPreview;
-
- public SoftwareCameraPreview(final Context context) {
- super(context);
- mPreview = new CameraPreview(this);
- getHolder().addCallback(new SurfaceHolder.Callback() {
- @Override
- public void surfaceCreated(final SurfaceHolder surfaceHolder) {
- CameraManager.get().setSurface(mPreview);
- }
-
- @Override
- public void surfaceChanged(final SurfaceHolder surfaceHolder, final int format, final int width,
- final int height) {
- CameraManager.get().setSurface(mPreview);
- }
-
- @Override
- public void surfaceDestroyed(final SurfaceHolder surfaceHolder) {
- CameraManager.get().setSurface(null);
- }
- });
- }
-
-
- @Override
- protected void onVisibilityChanged(final View changedView, final int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- mPreview.onVisibilityChanged(visibility);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mPreview.onDetachedFromWindow();
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mPreview.onAttachedToWindow();
- }
-
- @Override
- protected void onRestoreInstanceState(final Parcelable state) {
- super.onRestoreInstanceState(state);
- mPreview.onRestoreInstanceState();
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- widthMeasureSpec = mPreview.getWidthMeasureSpec(widthMeasureSpec, heightMeasureSpec);
- heightMeasureSpec = mPreview.getHeightMeasureSpec(widthMeasureSpec, heightMeasureSpec);
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
-
- @Override
- public View getView() {
- return this;
- }
-
- @Override
- public boolean isValid() {
- return getHolder() != null;
- }
-
- @Override
- public void startPreview(final Camera camera) throws IOException {
- camera.setPreviewDisplay(getHolder());
- }
-
- @Override
- public void onCameraPermissionGranted() {
- mPreview.onCameraPermissionGranted();
- }
-}
-
-
diff --git a/src/com/android/messaging/ui/mediapicker/SoundLevels.java b/src/com/android/messaging/ui/mediapicker/SoundLevels.java
deleted file mode 100644
index 6f4dca6..0000000
--- a/src/com/android/messaging/ui/mediapicker/SoundLevels.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * 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.mediapicker;
-
-import android.animation.ObjectAnimator;
-import android.animation.TimeAnimator;
-import android.animation.TimeAnimator.TimeListener;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
-
-import com.android.messaging.R;
-import com.android.messaging.util.LogUtil;
-
-/**
- * This view draws circular sound levels. By default the sound levels are black, unless
- * otherwise defined via {@link #mPrimaryLevelPaint}.
- */
-public class SoundLevels extends View {
- private static final String TAG = LogUtil.BUGLE_TAG;
- private static final boolean DEBUG = false;
-
- private boolean mCenterDefined;
- private int mCenterX;
- private int mCenterY;
-
- // Paint for the main level meter, most closely follows the mic.
- private final Paint mPrimaryLevelPaint;
-
- // The minimum size of the levels as a percentage of the max, that is the size when volume is 0.
- private final float mMinimumLevel;
-
- // The minimum size of the levels, that is the size when volume is 0.
- private final float mMinimumLevelSize;
-
- // The maximum size of the levels, that is the size when volume is 100.
- private final float mMaximumLevelSize;
-
- // Generates clock ticks for the animation using the global animation loop.
- private final TimeAnimator mSpeechLevelsAnimator;
-
- private float mCurrentVolume;
-
- // Indicates whether we should be animating the sound level.
- private boolean mIsEnabled;
-
- // Input level is pulled from here.
- private AudioLevelSource mLevelSource;
-
- public SoundLevels(final Context context) {
- this(context, null);
- }
-
- public SoundLevels(final Context context, final AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public SoundLevels(final Context context, final AttributeSet attrs, final int defStyle) {
- super(context, attrs, defStyle);
-
- // Safe source, replaced with system one when attached.
- mLevelSource = new AudioLevelSource();
- mLevelSource.setSpeechLevel(0);
-
- final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SoundLevels,
- defStyle, 0);
-
- mMaximumLevelSize = a.getDimensionPixelOffset(
- R.styleable.SoundLevels_maxLevelRadius, 0);
- mMinimumLevelSize = a.getDimensionPixelOffset(
- R.styleable.SoundLevels_minLevelRadius, 0);
- mMinimumLevel = mMinimumLevelSize / mMaximumLevelSize;
-
- mPrimaryLevelPaint = new Paint();
- mPrimaryLevelPaint.setColor(
- a.getColor(R.styleable.SoundLevels_primaryColor, Color.BLACK));
- mPrimaryLevelPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
-
- a.recycle();
-
- // This animator generates ticks that invalidate the
- // view so that the animation is synced with the global animation loop.
- // TODO: We could probably remove this in favor of using postInvalidateOnAnimation
- // which might improve things further.
- mSpeechLevelsAnimator = new TimeAnimator();
- mSpeechLevelsAnimator.setRepeatCount(ObjectAnimator.INFINITE);
- mSpeechLevelsAnimator.setTimeListener(new TimeListener() {
- @Override
- public void onTimeUpdate(final TimeAnimator animation, final long totalTime,
- final long deltaTime) {
- invalidate();
- }
- });
- }
-
- @Override
- protected void onDraw(final Canvas canvas) {
- if (!mIsEnabled) {
- return;
- }
-
- if (!mCenterDefined) {
- // One time computation here, because we can't rely on getWidth() to be computed at
- // constructor time or in onFinishInflate :(.
- mCenterX = getWidth() / 2;
- mCenterY = getWidth() / 2;
- mCenterDefined = true;
- }
-
- final int level = mLevelSource.getSpeechLevel();
- // Either ease towards the target level, or decay away from it depending on whether
- // its higher or lower than the current.
- if (level > mCurrentVolume) {
- mCurrentVolume = mCurrentVolume + ((level - mCurrentVolume) / 4);
- } else {
- mCurrentVolume = mCurrentVolume * 0.95f;
- }
-
- final float radius = mMinimumLevel + (1f - mMinimumLevel) * mCurrentVolume / 100;
- mPrimaryLevelPaint.setStyle(Style.FILL);
- canvas.drawCircle(mCenterX, mCenterY, radius * mMaximumLevelSize, mPrimaryLevelPaint);
- }
-
- public void setLevelSource(final AudioLevelSource source) {
- if (DEBUG) {
- Log.d(TAG, "Speech source set.");
- }
- mLevelSource = source;
- }
-
- private void startSpeechLevelsAnimator() {
- if (DEBUG) {
- Log.d(TAG, "startAnimator()");
- }
- if (!mSpeechLevelsAnimator.isStarted()) {
- mSpeechLevelsAnimator.start();
- }
- }
-
- private void stopSpeechLevelsAnimator() {
- if (DEBUG) {
- Log.d(TAG, "stopAnimator()");
- }
- if (mSpeechLevelsAnimator.isStarted()) {
- mSpeechLevelsAnimator.end();
- }
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- stopSpeechLevelsAnimator();
- }
-
- @Override
- public void setEnabled(final boolean enabled) {
- if (enabled == mIsEnabled) {
- return;
- }
- if (DEBUG) {
- Log.d("TAG", "setEnabled: " + enabled);
- }
- super.setEnabled(enabled);
- mIsEnabled = enabled;
- setKeepScreenOn(enabled);
- updateSpeechLevelsAnimatorState();
- }
-
- private void updateSpeechLevelsAnimatorState() {
- if (mIsEnabled) {
- startSpeechLevelsAnimator();
- } else {
- stopSpeechLevelsAnimator();
- }
- }
-
- /**
- * This is required to make the View findable by uiautomator
- */
- @Override
- public void onInitializeAccessibilityNodeInfo(final AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(SoundLevels.class.getCanonicalName());
- }
-
- /**
- * Set the alpha level of the sound circles.
- */
- public void setPrimaryColorAlpha(final int alpha) {
- mPrimaryLevelPaint.setAlpha(alpha);
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java b/src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java
deleted file mode 100644
index 92ed3c1..0000000
--- a/src/com/android/messaging/ui/mediapicker/camerafocus/FocusIndicator.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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.mediapicker.camerafocus;
-
-public interface FocusIndicator {
- public void showStart();
- public void showSuccess(boolean timeout);
- public void showFail(boolean timeout);
- public void clear();
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java b/src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java
deleted file mode 100644
index e620fc2..0000000
--- a/src/com/android/messaging/ui/mediapicker/camerafocus/FocusOverlayManager.java
+++ /dev/null
@@ -1,589 +0,0 @@
-/*
- * 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.mediapicker.camerafocus;
-
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.hardware.Camera.Area;
-import android.hardware.Camera.Parameters;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-
-import com.android.messaging.util.Assert;
-import com.android.messaging.util.LogUtil;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/* A class that handles everything about focus in still picture mode.
- * This also handles the metering area because it is the same as focus area.
- *
- * The test cases:
- * (1) The camera has continuous autofocus. Move the camera. Take a picture when
- * CAF is not in progress.
- * (2) The camera has continuous autofocus. Move the camera. Take a picture when
- * CAF is in progress.
- * (3) The camera has face detection. Point the camera at some faces. Hold the
- * shutter. Release to take a picture.
- * (4) The camera has face detection. Point the camera at some faces. Single tap
- * the shutter to take a picture.
- * (5) The camera has autofocus. Single tap the shutter to take a picture.
- * (6) The camera has autofocus. Hold the shutter. Release to take a picture.
- * (7) The camera has no autofocus. Single tap the shutter and take a picture.
- * (8) The camera has autofocus and supports focus area. Touch the screen to
- * trigger autofocus. Take a picture.
- * (9) The camera has autofocus and supports focus area. Touch the screen to
- * trigger autofocus. Wait until it times out.
- * (10) The camera has no autofocus and supports metering area. Touch the screen
- * to change metering area.
- */
-public class FocusOverlayManager {
- private static final String TAG = LogUtil.BUGLE_TAG;
- private static final String TRUE = "true";
- private static final String AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
- private static final String AUTO_WHITE_BALANCE_LOCK_SUPPORTED =
- "auto-whitebalance-lock-supported";
-
- private static final int RESET_TOUCH_FOCUS = 0;
- private static final int RESET_TOUCH_FOCUS_DELAY = 3000;
-
- private int mState = STATE_IDLE;
- private static final int STATE_IDLE = 0; // Focus is not active.
- private static final int STATE_FOCUSING = 1; // Focus is in progress.
- // Focus is in progress and the camera should take a picture after focus finishes.
- private static final int STATE_FOCUSING_SNAP_ON_FINISH = 2;
- private static final int STATE_SUCCESS = 3; // Focus finishes and succeeds.
- private static final int STATE_FAIL = 4; // Focus finishes and fails.
-
- private boolean mInitialized;
- private boolean mFocusAreaSupported;
- private boolean mMeteringAreaSupported;
- private boolean mLockAeAwbNeeded;
- private boolean mAeAwbLock;
- private Matrix mMatrix;
-
- private PieRenderer mPieRenderer;
-
- private int mPreviewWidth; // The width of the preview frame layout.
- private int mPreviewHeight; // The height of the preview frame layout.
- private boolean mMirror; // true if the camera is front-facing.
- private int mDisplayOrientation;
- private List<Object> mFocusArea; // focus area in driver format
- private List<Object> mMeteringArea; // metering area in driver format
- private String mFocusMode;
- private String mOverrideFocusMode;
- private Parameters mParameters;
- private Handler mHandler;
- Listener mListener;
-
- public interface Listener {
- public void autoFocus();
- public void cancelAutoFocus();
- public boolean capture();
- public void setFocusParameters();
- }
-
- private class MainHandler extends Handler {
- public MainHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case RESET_TOUCH_FOCUS: {
- cancelAutoFocus();
- break;
- }
- }
- }
- }
-
- public FocusOverlayManager(Listener listener, Looper looper) {
- mHandler = new MainHandler(looper);
- mMatrix = new Matrix();
- mListener = listener;
- }
-
- public void setFocusRenderer(PieRenderer renderer) {
- mPieRenderer = renderer;
- mInitialized = (mMatrix != null);
- }
-
- public void setParameters(Parameters parameters) {
- // parameters can only be null when onConfigurationChanged is called
- // before camera is open. We will just return in this case, because
- // parameters will be set again later with the right parameters after
- // camera is open.
- if (parameters == null) {
- return;
- }
- mParameters = parameters;
- mFocusAreaSupported = isFocusAreaSupported(parameters);
- mMeteringAreaSupported = isMeteringAreaSupported(parameters);
- mLockAeAwbNeeded = (isAutoExposureLockSupported(mParameters) ||
- isAutoWhiteBalanceLockSupported(mParameters));
- }
-
- public void setPreviewSize(int previewWidth, int previewHeight) {
- if (mPreviewWidth != previewWidth || mPreviewHeight != previewHeight) {
- mPreviewWidth = previewWidth;
- mPreviewHeight = previewHeight;
- setMatrix();
- }
- }
-
- public void setMirror(boolean mirror) {
- mMirror = mirror;
- setMatrix();
- }
-
- public void setDisplayOrientation(int displayOrientation) {
- mDisplayOrientation = displayOrientation;
- setMatrix();
- }
-
- private void setMatrix() {
- if (mPreviewWidth != 0 && mPreviewHeight != 0) {
- Matrix matrix = new Matrix();
- prepareMatrix(matrix, mMirror, mDisplayOrientation,
- mPreviewWidth, mPreviewHeight);
- // In face detection, the matrix converts the driver coordinates to UI
- // coordinates. In tap focus, the inverted matrix converts the UI
- // coordinates to driver coordinates.
- matrix.invert(mMatrix);
- mInitialized = (mPieRenderer != null);
- }
- }
-
- private void lockAeAwbIfNeeded() {
- if (mLockAeAwbNeeded && !mAeAwbLock) {
- mAeAwbLock = true;
- mListener.setFocusParameters();
- }
- }
-
- private void unlockAeAwbIfNeeded() {
- if (mLockAeAwbNeeded && mAeAwbLock && (mState != STATE_FOCUSING_SNAP_ON_FINISH)) {
- mAeAwbLock = false;
- mListener.setFocusParameters();
- }
- }
-
- public void onShutterDown() {
- if (!mInitialized) {
- return;
- }
-
- boolean autoFocusCalled = false;
- if (needAutoFocusCall()) {
- // Do not focus if touch focus has been triggered.
- if (mState != STATE_SUCCESS && mState != STATE_FAIL) {
- autoFocus();
- autoFocusCalled = true;
- }
- }
-
- if (!autoFocusCalled) {
- lockAeAwbIfNeeded();
- }
- }
-
- public void onShutterUp() {
- if (!mInitialized) {
- return;
- }
-
- if (needAutoFocusCall()) {
- // User releases half-pressed focus key.
- if (mState == STATE_FOCUSING || mState == STATE_SUCCESS
- || mState == STATE_FAIL) {
- cancelAutoFocus();
- }
- }
-
- // Unlock AE and AWB after cancelAutoFocus. Camera API does not
- // guarantee setParameters can be called during autofocus.
- unlockAeAwbIfNeeded();
- }
-
- public void doSnap() {
- if (!mInitialized) {
- return;
- }
-
- // If the user has half-pressed the shutter and focus is completed, we
- // can take the photo right away. If the focus mode is infinity, we can
- // also take the photo.
- if (!needAutoFocusCall() || (mState == STATE_SUCCESS || mState == STATE_FAIL)) {
- capture();
- } else if (mState == STATE_FOCUSING) {
- // Half pressing the shutter (i.e. the focus button event) will
- // already have requested AF for us, so just request capture on
- // focus here.
- mState = STATE_FOCUSING_SNAP_ON_FINISH;
- } else if (mState == STATE_IDLE) {
- // We didn't do focus. This can happen if the user press focus key
- // while the snapshot is still in progress. The user probably wants
- // the next snapshot as soon as possible, so we just do a snapshot
- // without focusing again.
- capture();
- }
- }
-
- public void onAutoFocus(boolean focused, boolean shutterButtonPressed) {
- if (mState == STATE_FOCUSING_SNAP_ON_FINISH) {
- // Take the picture no matter focus succeeds or fails. No need
- // to play the AF sound if we're about to play the shutter
- // sound.
- if (focused) {
- mState = STATE_SUCCESS;
- } else {
- mState = STATE_FAIL;
- }
- updateFocusUI();
- capture();
- } else if (mState == STATE_FOCUSING) {
- // This happens when (1) user is half-pressing the focus key or
- // (2) touch focus is triggered. Play the focus tone. Do not
- // take the picture now.
- if (focused) {
- mState = STATE_SUCCESS;
- } else {
- mState = STATE_FAIL;
- }
- updateFocusUI();
- // If this is triggered by touch focus, cancel focus after a
- // while.
- if (mFocusArea != null) {
- mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);
- }
- if (shutterButtonPressed) {
- // Lock AE & AWB so users can half-press shutter and recompose.
- lockAeAwbIfNeeded();
- }
- } else if (mState == STATE_IDLE) {
- // User has released the focus key before focus completes.
- // Do nothing.
- }
- }
-
- public void onAutoFocusMoving(boolean moving) {
- if (!mInitialized) {
- return;
- }
-
- // Ignore if we have requested autofocus. This method only handles
- // continuous autofocus.
- if (mState != STATE_IDLE) {
- return;
- }
-
- if (moving) {
- mPieRenderer.showStart();
- } else {
- mPieRenderer.showSuccess(true);
- }
- }
-
- private void initializeFocusAreas(int focusWidth, int focusHeight,
- int x, int y, int previewWidth, int previewHeight) {
- if (mFocusArea == null) {
- mFocusArea = new ArrayList<Object>();
- mFocusArea.add(new Area(new Rect(), 1));
- }
-
- // Convert the coordinates to driver format.
- calculateTapArea(focusWidth, focusHeight, 1f, x, y, previewWidth, previewHeight,
- ((Area) mFocusArea.get(0)).rect);
- }
-
- private void initializeMeteringAreas(int focusWidth, int focusHeight,
- int x, int y, int previewWidth, int previewHeight) {
- if (mMeteringArea == null) {
- mMeteringArea = new ArrayList<Object>();
- mMeteringArea.add(new Area(new Rect(), 1));
- }
-
- // Convert the coordinates to driver format.
- // AE area is bigger because exposure is sensitive and
- // easy to over- or underexposure if area is too small.
- calculateTapArea(focusWidth, focusHeight, 1.5f, x, y, previewWidth, previewHeight,
- ((Area) mMeteringArea.get(0)).rect);
- }
-
- public void onSingleTapUp(int x, int y) {
- if (!mInitialized || mState == STATE_FOCUSING_SNAP_ON_FINISH) {
- return;
- }
-
- // Let users be able to cancel previous touch focus.
- if ((mFocusArea != null) && (mState == STATE_FOCUSING ||
- mState == STATE_SUCCESS || mState == STATE_FAIL)) {
- cancelAutoFocus();
- }
- // Initialize variables.
- int focusWidth = mPieRenderer.getSize();
- int focusHeight = mPieRenderer.getSize();
- if (focusWidth == 0 || mPieRenderer.getWidth() == 0 || mPieRenderer.getHeight() == 0) {
- return;
- }
- int previewWidth = mPreviewWidth;
- int previewHeight = mPreviewHeight;
- // Initialize mFocusArea.
- if (mFocusAreaSupported) {
- initializeFocusAreas(focusWidth, focusHeight, x, y, previewWidth, previewHeight);
- }
- // Initialize mMeteringArea.
- if (mMeteringAreaSupported) {
- initializeMeteringAreas(focusWidth, focusHeight, x, y, previewWidth, previewHeight);
- }
-
- // Use margin to set the focus indicator to the touched area.
- mPieRenderer.setFocus(x, y);
-
- // Set the focus area and metering area.
- mListener.setFocusParameters();
- if (mFocusAreaSupported) {
- autoFocus();
- } else { // Just show the indicator in all other cases.
- updateFocusUI();
- // Reset the metering area in 3 seconds.
- mHandler.removeMessages(RESET_TOUCH_FOCUS);
- mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);
- }
- }
-
- public void onPreviewStarted() {
- mState = STATE_IDLE;
- }
-
- public void onPreviewStopped() {
- // If auto focus was in progress, it would have been stopped.
- mState = STATE_IDLE;
- resetTouchFocus();
- updateFocusUI();
- }
-
- public void onCameraReleased() {
- onPreviewStopped();
- }
-
- private void autoFocus() {
- LogUtil.v(TAG, "Start autofocus.");
- mListener.autoFocus();
- mState = STATE_FOCUSING;
- updateFocusUI();
- mHandler.removeMessages(RESET_TOUCH_FOCUS);
- }
-
- private void cancelAutoFocus() {
- LogUtil.v(TAG, "Cancel autofocus.");
-
- // Reset the tap area before calling mListener.cancelAutofocus.
- // Otherwise, focus mode stays at auto and the tap area passed to the
- // driver is not reset.
- resetTouchFocus();
- mListener.cancelAutoFocus();
- mState = STATE_IDLE;
- updateFocusUI();
- mHandler.removeMessages(RESET_TOUCH_FOCUS);
- }
-
- private void capture() {
- if (mListener.capture()) {
- mState = STATE_IDLE;
- mHandler.removeMessages(RESET_TOUCH_FOCUS);
- }
- }
-
- public String getFocusMode() {
- if (mOverrideFocusMode != null) {
- return mOverrideFocusMode;
- }
- List<String> supportedFocusModes = mParameters.getSupportedFocusModes();
-
- if (mFocusAreaSupported && mFocusArea != null) {
- // Always use autofocus in tap-to-focus.
- mFocusMode = Parameters.FOCUS_MODE_AUTO;
- } else {
- mFocusMode = Parameters.FOCUS_MODE_CONTINUOUS_PICTURE;
- }
-
- if (!isSupported(mFocusMode, supportedFocusModes)) {
- // For some reasons, the driver does not support the current
- // focus mode. Fall back to auto.
- if (isSupported(Parameters.FOCUS_MODE_AUTO,
- mParameters.getSupportedFocusModes())) {
- mFocusMode = Parameters.FOCUS_MODE_AUTO;
- } else {
- mFocusMode = mParameters.getFocusMode();
- }
- }
- return mFocusMode;
- }
-
- public List getFocusAreas() {
- return mFocusArea;
- }
-
- public List getMeteringAreas() {
- return mMeteringArea;
- }
-
- public void updateFocusUI() {
- if (!mInitialized) {
- return;
- }
- FocusIndicator focusIndicator = mPieRenderer;
-
- if (mState == STATE_IDLE) {
- if (mFocusArea == null) {
- focusIndicator.clear();
- } else {
- // Users touch on the preview and the indicator represents the
- // metering area. Either focus area is not supported or
- // autoFocus call is not required.
- focusIndicator.showStart();
- }
- } else if (mState == STATE_FOCUSING || mState == STATE_FOCUSING_SNAP_ON_FINISH) {
- focusIndicator.showStart();
- } else {
- if (Parameters.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusMode)) {
- // TODO: check HAL behavior and decide if this can be removed.
- focusIndicator.showSuccess(false);
- } else if (mState == STATE_SUCCESS) {
- focusIndicator.showSuccess(false);
- } else if (mState == STATE_FAIL) {
- focusIndicator.showFail(false);
- }
- }
- }
-
- public void resetTouchFocus() {
- if (!mInitialized) {
- return;
- }
-
- // Put focus indicator to the center. clear reset position
- mPieRenderer.clear();
-
- mFocusArea = null;
- mMeteringArea = null;
- }
-
- private void calculateTapArea(int focusWidth, int focusHeight, float areaMultiple,
- int x, int y, int previewWidth, int previewHeight, Rect rect) {
- int areaWidth = (int) (focusWidth * areaMultiple);
- int areaHeight = (int) (focusHeight * areaMultiple);
- int left = clamp(x - areaWidth / 2, 0, previewWidth - areaWidth);
- int top = clamp(y - areaHeight / 2, 0, previewHeight - areaHeight);
-
- RectF rectF = new RectF(left, top, left + areaWidth, top + areaHeight);
- mMatrix.mapRect(rectF);
- rectFToRect(rectF, rect);
- }
-
- /* package */ int getFocusState() {
- return mState;
- }
-
- public boolean isFocusCompleted() {
- return mState == STATE_SUCCESS || mState == STATE_FAIL;
- }
-
- public boolean isFocusingSnapOnFinish() {
- return mState == STATE_FOCUSING_SNAP_ON_FINISH;
- }
-
- public void removeMessages() {
- mHandler.removeMessages(RESET_TOUCH_FOCUS);
- }
-
- public void overrideFocusMode(String focusMode) {
- mOverrideFocusMode = focusMode;
- }
-
- public void setAeAwbLock(boolean lock) {
- mAeAwbLock = lock;
- }
-
- public boolean getAeAwbLock() {
- return mAeAwbLock;
- }
-
- private boolean needAutoFocusCall() {
- String focusMode = getFocusMode();
- return !(focusMode.equals(Parameters.FOCUS_MODE_INFINITY)
- || focusMode.equals(Parameters.FOCUS_MODE_FIXED)
- || focusMode.equals(Parameters.FOCUS_MODE_EDOF));
- }
-
- public static boolean isAutoExposureLockSupported(Parameters params) {
- return TRUE.equals(params.get(AUTO_EXPOSURE_LOCK_SUPPORTED));
- }
-
- public static boolean isAutoWhiteBalanceLockSupported(Parameters params) {
- return TRUE.equals(params.get(AUTO_WHITE_BALANCE_LOCK_SUPPORTED));
- }
-
- public static boolean isSupported(String value, List<String> supported) {
- return supported != null && supported.indexOf(value) >= 0;
- }
-
- public static boolean isMeteringAreaSupported(Parameters params) {
- return params.getMaxNumMeteringAreas() > 0;
- }
-
- public static boolean isFocusAreaSupported(Parameters params) {
- return (params.getMaxNumFocusAreas() > 0
- && isSupported(Parameters.FOCUS_MODE_AUTO,
- params.getSupportedFocusModes()));
- }
-
- public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation,
- int viewWidth, int viewHeight) {
- // Need mirror for front camera.
- matrix.setScale(mirror ? -1 : 1, 1);
- // This is the value for android.hardware.Camera.setDisplayOrientation.
- matrix.postRotate(displayOrientation);
- // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
- // UI coordinates range from (0, 0) to (width, height).
- matrix.postScale(viewWidth / 2000f, viewHeight / 2000f);
- matrix.postTranslate(viewWidth / 2f, viewHeight / 2f);
- }
-
- public static int clamp(int x, int min, int max) {
- Assert.isTrue(max >= min);
- if (x > max) {
- return max;
- }
- if (x < min) {
- return min;
- }
- return x;
- }
-
- public static void rectFToRect(RectF rectF, Rect rect) {
- rect.left = Math.round(rectF.left);
- rect.top = Math.round(rectF.top);
- rect.right = Math.round(rectF.right);
- rect.bottom = Math.round(rectF.bottom);
- }
-}
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java b/src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java
deleted file mode 100644
index df6734f..0000000
--- a/src/com/android/messaging/ui/mediapicker/camerafocus/OverlayRenderer.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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.mediapicker.camerafocus;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.view.MotionEvent;
-
-public abstract class OverlayRenderer implements RenderOverlay.Renderer {
-
- private static final String TAG = "CAM OverlayRenderer";
- protected RenderOverlay mOverlay;
-
- protected int mLeft, mTop, mRight, mBottom;
-
- protected boolean mVisible;
-
- public void setVisible(boolean vis) {
- mVisible = vis;
- update();
- }
-
- public boolean isVisible() {
- return mVisible;
- }
-
- // default does not handle touch
- @Override
- public boolean handlesTouch() {
- return false;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent evt) {
- return false;
- }
-
- public abstract void onDraw(Canvas canvas);
-
- public void draw(Canvas canvas) {
- if (mVisible) {
- onDraw(canvas);
- }
- }
-
- @Override
- public void setOverlay(RenderOverlay overlay) {
- mOverlay = overlay;
- }
-
- @Override
- public void layout(int left, int top, int right, int bottom) {
- mLeft = left;
- mRight = right;
- mTop = top;
- mBottom = bottom;
- }
-
- protected Context getContext() {
- if (mOverlay != null) {
- return mOverlay.getContext();
- } else {
- return null;
- }
- }
-
- public int getWidth() {
- return mRight - mLeft;
- }
-
- public int getHeight() {
- return mBottom - mTop;
- }
-
- protected void update() {
- if (mOverlay != null) {
- mOverlay.update();
- }
- }
-
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java b/src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java
deleted file mode 100644
index c602852..0000000
--- a/src/com/android/messaging/ui/mediapicker/camerafocus/PieItem.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * 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.mediapicker.camerafocus;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Path;
-import android.graphics.drawable.Drawable;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Pie menu item
- */
-public class PieItem {
-
- public static interface OnClickListener {
- void onClick(PieItem item);
- }
-
- private Drawable mDrawable;
- private int level;
- private float mCenter;
- private float start;
- private float sweep;
- private float animate;
- private int inner;
- private int outer;
- private boolean mSelected;
- private boolean mEnabled;
- private List<PieItem> mItems;
- private Path mPath;
- private OnClickListener mOnClickListener;
- private float mAlpha;
-
- // Gray out the view when disabled
- private static final float ENABLED_ALPHA = 1;
- private static final float DISABLED_ALPHA = (float) 0.3;
- private boolean mChangeAlphaWhenDisabled = true;
-
- public PieItem(Drawable drawable, int level) {
- mDrawable = drawable;
- this.level = level;
- setAlpha(1f);
- mEnabled = true;
- setAnimationAngle(getAnimationAngle());
- start = -1;
- mCenter = -1;
- }
-
- public boolean hasItems() {
- return mItems != null;
- }
-
- public List<PieItem> getItems() {
- return mItems;
- }
-
- public void addItem(PieItem item) {
- if (mItems == null) {
- mItems = new ArrayList<PieItem>();
- }
- mItems.add(item);
- }
-
- public void setPath(Path p) {
- mPath = p;
- }
-
- public Path getPath() {
- return mPath;
- }
-
- public void setChangeAlphaWhenDisabled (boolean enable) {
- mChangeAlphaWhenDisabled = enable;
- }
-
- public void setAlpha(float alpha) {
- mAlpha = alpha;
- mDrawable.setAlpha((int) (255 * alpha));
- }
-
- public void setAnimationAngle(float a) {
- animate = a;
- }
-
- public float getAnimationAngle() {
- return animate;
- }
-
- public void setEnabled(boolean enabled) {
- mEnabled = enabled;
- if (mChangeAlphaWhenDisabled) {
- if (mEnabled) {
- setAlpha(ENABLED_ALPHA);
- } else {
- setAlpha(DISABLED_ALPHA);
- }
- }
- }
-
- public boolean isEnabled() {
- return mEnabled;
- }
-
- public void setSelected(boolean s) {
- mSelected = s;
- }
-
- public boolean isSelected() {
- return mSelected;
- }
-
- public int getLevel() {
- return level;
- }
-
- public void setGeometry(float st, float sw, int inside, int outside) {
- start = st;
- sweep = sw;
- inner = inside;
- outer = outside;
- }
-
- public void setFixedSlice(float center, float sweep) {
- mCenter = center;
- this.sweep = sweep;
- }
-
- public float getCenter() {
- return mCenter;
- }
-
- public float getStart() {
- return start;
- }
-
- public float getStartAngle() {
- return start + animate;
- }
-
- public float getSweep() {
- return sweep;
- }
-
- public int getInnerRadius() {
- return inner;
- }
-
- public int getOuterRadius() {
- return outer;
- }
-
- public void setOnClickListener(OnClickListener listener) {
- mOnClickListener = listener;
- }
-
- public void performClick() {
- if (mOnClickListener != null) {
- mOnClickListener.onClick(this);
- }
- }
-
- public int getIntrinsicWidth() {
- return mDrawable.getIntrinsicWidth();
- }
-
- public int getIntrinsicHeight() {
- return mDrawable.getIntrinsicHeight();
- }
-
- public void setBounds(int left, int top, int right, int bottom) {
- mDrawable.setBounds(left, top, right, bottom);
- }
-
- public void draw(Canvas canvas) {
- mDrawable.draw(canvas);
- }
-
- public void setImageResource(Context context, int resId) {
- Drawable d = context.getResources().getDrawable(resId).mutate();
- d.setBounds(mDrawable.getBounds());
- mDrawable = d;
- setAlpha(mAlpha);
- }
-
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java b/src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java
deleted file mode 100644
index ce8ca00..0000000
--- a/src/com/android/messaging/ui/mediapicker/camerafocus/PieRenderer.java
+++ /dev/null
@@ -1,825 +0,0 @@
-/*
- * 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.mediapicker.camerafocus;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.RectF;
-import android.os.Handler;
-import android.os.Message;
-import android.view.MotionEvent;
-import android.view.ViewConfiguration;
-import android.view.animation.Animation;
-import android.view.animation.Animation.AnimationListener;
-import android.view.animation.LinearInterpolator;
-import android.view.animation.Transformation;
-import com.android.messaging.R;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class PieRenderer extends OverlayRenderer
- implements FocusIndicator {
- // Sometimes continuous autofocus starts and stops several times quickly.
- // These states are used to make sure the animation is run for at least some
- // time.
- private volatile int mState;
- private ScaleAnimation mAnimation = new ScaleAnimation();
- private static final int STATE_IDLE = 0;
- private static final int STATE_FOCUSING = 1;
- private static final int STATE_FINISHING = 2;
- private static final int STATE_PIE = 8;
-
- private Runnable mDisappear = new Disappear();
- private Animation.AnimationListener mEndAction = new EndAction();
- private static final int SCALING_UP_TIME = 600;
- private static final int SCALING_DOWN_TIME = 100;
- private static final int DISAPPEAR_TIMEOUT = 200;
- private static final int DIAL_HORIZONTAL = 157;
-
- private static final long PIE_FADE_IN_DURATION = 200;
- private static final long PIE_XFADE_DURATION = 200;
- private static final long PIE_SELECT_FADE_DURATION = 300;
-
- private static final int MSG_OPEN = 0;
- private static final int MSG_CLOSE = 1;
- private static final float PIE_SWEEP = (float) (Math.PI * 2 / 3);
- // geometry
- private Point mCenter;
- private int mRadius;
- private int mRadiusInc;
-
- // the detection if touch is inside a slice is offset
- // inbounds by this amount to allow the selection to show before the
- // finger covers it
- private int mTouchOffset;
-
- private List<PieItem> mItems;
-
- private PieItem mOpenItem;
-
- private Paint mSelectedPaint;
- private Paint mSubPaint;
-
- // touch handling
- private PieItem mCurrentItem;
-
- private Paint mFocusPaint;
- private int mSuccessColor;
- private int mFailColor;
- private int mCircleSize;
- private int mFocusX;
- private int mFocusY;
- private int mCenterX;
- private int mCenterY;
-
- private int mDialAngle;
- private RectF mCircle;
- private RectF mDial;
- private Point mPoint1;
- private Point mPoint2;
- private int mStartAnimationAngle;
- private boolean mFocused;
- private int mInnerOffset;
- private int mOuterStroke;
- private int mInnerStroke;
- private boolean mTapMode;
- private boolean mBlockFocus;
- private int mTouchSlopSquared;
- private Point mDown;
- private boolean mOpening;
- private LinearAnimation mXFade;
- private LinearAnimation mFadeIn;
- private volatile boolean mFocusCancelled;
-
- private Handler mHandler = new Handler() {
- public void handleMessage(Message msg) {
- switch(msg.what) {
- case MSG_OPEN:
- if (mListener != null) {
- mListener.onPieOpened(mCenter.x, mCenter.y);
- }
- break;
- case MSG_CLOSE:
- if (mListener != null) {
- mListener.onPieClosed();
- }
- break;
- }
- }
- };
-
- private PieListener mListener;
-
- public static interface PieListener {
- public void onPieOpened(int centerX, int centerY);
- public void onPieClosed();
- }
-
- public void setPieListener(PieListener pl) {
- mListener = pl;
- }
-
- public PieRenderer(Context context) {
- init(context);
- }
-
- private void init(Context ctx) {
- setVisible(false);
- mItems = new ArrayList<PieItem>();
- Resources res = ctx.getResources();
- mRadius = (int) res.getDimensionPixelSize(R.dimen.pie_radius_start);
- mCircleSize = mRadius - res.getDimensionPixelSize(R.dimen.focus_radius_offset);
- mRadiusInc = (int) res.getDimensionPixelSize(R.dimen.pie_radius_increment);
- mTouchOffset = (int) res.getDimensionPixelSize(R.dimen.pie_touch_offset);
- mCenter = new Point(0, 0);
- mSelectedPaint = new Paint();
- mSelectedPaint.setColor(Color.argb(255, 51, 181, 229));
- mSelectedPaint.setAntiAlias(true);
- mSubPaint = new Paint();
- mSubPaint.setAntiAlias(true);
- mSubPaint.setColor(Color.argb(200, 250, 230, 128));
- mFocusPaint = new Paint();
- mFocusPaint.setAntiAlias(true);
- mFocusPaint.setColor(Color.WHITE);
- mFocusPaint.setStyle(Paint.Style.STROKE);
- mSuccessColor = Color.GREEN;
- mFailColor = Color.RED;
- mCircle = new RectF();
- mDial = new RectF();
- mPoint1 = new Point();
- mPoint2 = new Point();
- mInnerOffset = res.getDimensionPixelSize(R.dimen.focus_inner_offset);
- mOuterStroke = res.getDimensionPixelSize(R.dimen.focus_outer_stroke);
- mInnerStroke = res.getDimensionPixelSize(R.dimen.focus_inner_stroke);
- mState = STATE_IDLE;
- mBlockFocus = false;
- mTouchSlopSquared = ViewConfiguration.get(ctx).getScaledTouchSlop();
- mTouchSlopSquared = mTouchSlopSquared * mTouchSlopSquared;
- mDown = new Point();
- }
-
- public boolean showsItems() {
- return mTapMode;
- }
-
- public void addItem(PieItem item) {
- // add the item to the pie itself
- mItems.add(item);
- }
-
- public void removeItem(PieItem item) {
- mItems.remove(item);
- }
-
- public void clearItems() {
- mItems.clear();
- }
-
- public void showInCenter() {
- if ((mState == STATE_PIE) && isVisible()) {
- mTapMode = false;
- show(false);
- } else {
- if (mState != STATE_IDLE) {
- cancelFocus();
- }
- mState = STATE_PIE;
- setCenter(mCenterX, mCenterY);
- mTapMode = true;
- show(true);
- }
- }
-
- public void hide() {
- show(false);
- }
-
- /**
- * guaranteed has center set
- * @param show
- */
- private void show(boolean show) {
- if (show) {
- mState = STATE_PIE;
- // ensure clean state
- mCurrentItem = null;
- mOpenItem = null;
- for (PieItem item : mItems) {
- item.setSelected(false);
- }
- layoutPie();
- fadeIn();
- } else {
- mState = STATE_IDLE;
- mTapMode = false;
- if (mXFade != null) {
- mXFade.cancel();
- }
- }
- setVisible(show);
- mHandler.sendEmptyMessage(show ? MSG_OPEN : MSG_CLOSE);
- }
-
- private void fadeIn() {
- mFadeIn = new LinearAnimation(0, 1);
- mFadeIn.setDuration(PIE_FADE_IN_DURATION);
- mFadeIn.setAnimationListener(new AnimationListener() {
- @Override
- public void onAnimationStart(Animation animation) {
- }
-
- @Override
- public void onAnimationEnd(Animation animation) {
- mFadeIn = null;
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) {
- }
- });
- mFadeIn.startNow();
- mOverlay.startAnimation(mFadeIn);
- }
-
- public void setCenter(int x, int y) {
- mCenter.x = x;
- mCenter.y = y;
- // when using the pie menu, align the focus ring
- alignFocus(x, y);
- }
-
- private void layoutPie() {
- int rgap = 2;
- int inner = mRadius + rgap;
- int outer = mRadius + mRadiusInc - rgap;
- int gap = 1;
- layoutItems(mItems, (float) (Math.PI / 2), inner, outer, gap);
- }
-
- private void layoutItems(List<PieItem> items, float centerAngle, int inner,
- int outer, int gap) {
- float emptyangle = PIE_SWEEP / 16;
- float sweep = (float) (PIE_SWEEP - 2 * emptyangle) / items.size();
- float angle = centerAngle - PIE_SWEEP / 2 + emptyangle + sweep / 2;
- // check if we have custom geometry
- // first item we find triggers custom sweep for all
- // this allows us to re-use the path
- for (PieItem item : items) {
- if (item.getCenter() >= 0) {
- sweep = item.getSweep();
- break;
- }
- }
- Path path = makeSlice(getDegrees(0) - gap, getDegrees(sweep) + gap,
- outer, inner, mCenter);
- for (PieItem item : items) {
- // shared between items
- item.setPath(path);
- if (item.getCenter() >= 0) {
- angle = item.getCenter();
- }
- int w = item.getIntrinsicWidth();
- int h = item.getIntrinsicHeight();
- // move views to outer border
- int r = inner + (outer - inner) * 2 / 3;
- int x = (int) (r * Math.cos(angle));
- int y = mCenter.y - (int) (r * Math.sin(angle)) - h / 2;
- x = mCenter.x + x - w / 2;
- item.setBounds(x, y, x + w, y + h);
- float itemstart = angle - sweep / 2;
- item.setGeometry(itemstart, sweep, inner, outer);
- if (item.hasItems()) {
- layoutItems(item.getItems(), angle, inner,
- outer + mRadiusInc / 2, gap);
- }
- angle += sweep;
- }
- }
-
- private Path makeSlice(float start, float end, int outer, int inner, Point center) {
- RectF bb =
- new RectF(center.x - outer, center.y - outer, center.x + outer,
- center.y + outer);
- RectF bbi =
- new RectF(center.x - inner, center.y - inner, center.x + inner,
- center.y + inner);
- Path path = new Path();
- path.arcTo(bb, start, end - start, true);
- path.arcTo(bbi, end, start - end);
- path.close();
- return path;
- }
-
- /**
- * converts a
- * @param angle from 0..PI to Android degrees (clockwise starting at 3 o'clock)
- * @return skia angle
- */
- private float getDegrees(double angle) {
- return (float) (360 - 180 * angle / Math.PI);
- }
-
- private void startFadeOut() {
- mOverlay.animate().alpha(0).setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- deselect();
- show(false);
- mOverlay.setAlpha(1);
- super.onAnimationEnd(animation);
- }
- }).setDuration(PIE_SELECT_FADE_DURATION);
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- float alpha = 1;
- if (mXFade != null) {
- alpha = mXFade.getValue();
- } else if (mFadeIn != null) {
- alpha = mFadeIn.getValue();
- }
- int state = canvas.save();
- if (mFadeIn != null) {
- float sf = 0.9f + alpha * 0.1f;
- canvas.scale(sf, sf, mCenter.x, mCenter.y);
- }
- drawFocus(canvas);
- if (mState == STATE_FINISHING) {
- canvas.restoreToCount(state);
- return;
- }
- if ((mOpenItem == null) || (mXFade != null)) {
- // draw base menu
- for (PieItem item : mItems) {
- drawItem(canvas, item, alpha);
- }
- }
- if (mOpenItem != null) {
- for (PieItem inner : mOpenItem.getItems()) {
- drawItem(canvas, inner, (mXFade != null) ? (1 - 0.5f * alpha) : 1);
- }
- }
- canvas.restoreToCount(state);
- }
-
- private void drawItem(Canvas canvas, PieItem item, float alpha) {
- if (mState == STATE_PIE) {
- if (item.getPath() != null) {
- if (item.isSelected()) {
- Paint p = mSelectedPaint;
- int state = canvas.save();
- float r = getDegrees(item.getStartAngle());
- canvas.rotate(r, mCenter.x, mCenter.y);
- canvas.drawPath(item.getPath(), p);
- canvas.restoreToCount(state);
- }
- alpha = alpha * (item.isEnabled() ? 1 : 0.3f);
- // draw the item view
- item.setAlpha(alpha);
- item.draw(canvas);
- }
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent evt) {
- float x = evt.getX();
- float y = evt.getY();
- int action = evt.getActionMasked();
- PointF polar = getPolar(x, y, !(mTapMode));
- if (MotionEvent.ACTION_DOWN == action) {
- mDown.x = (int) evt.getX();
- mDown.y = (int) evt.getY();
- mOpening = false;
- if (mTapMode) {
- PieItem item = findItem(polar);
- if ((item != null) && (mCurrentItem != item)) {
- mState = STATE_PIE;
- onEnter(item);
- }
- } else {
- setCenter((int) x, (int) y);
- show(true);
- }
- return true;
- } else if (MotionEvent.ACTION_UP == action) {
- if (isVisible()) {
- PieItem item = mCurrentItem;
- if (mTapMode) {
- item = findItem(polar);
- if (item != null && mOpening) {
- mOpening = false;
- return true;
- }
- }
- if (item == null) {
- mTapMode = false;
- show(false);
- } else if (!mOpening
- && !item.hasItems()) {
- item.performClick();
- startFadeOut();
- mTapMode = false;
- }
- return true;
- }
- } else if (MotionEvent.ACTION_CANCEL == action) {
- if (isVisible() || mTapMode) {
- show(false);
- }
- deselect();
- return false;
- } else if (MotionEvent.ACTION_MOVE == action) {
- if (polar.y < mRadius) {
- if (mOpenItem != null) {
- mOpenItem = null;
- } else {
- deselect();
- }
- return false;
- }
- PieItem item = findItem(polar);
- boolean moved = hasMoved(evt);
- if ((item != null) && (mCurrentItem != item) && (!mOpening || moved)) {
- // only select if we didn't just open or have moved past slop
- mOpening = false;
- if (moved) {
- // switch back to swipe mode
- mTapMode = false;
- }
- onEnter(item);
- }
- }
- return false;
- }
-
- private boolean hasMoved(MotionEvent e) {
- return mTouchSlopSquared < (e.getX() - mDown.x) * (e.getX() - mDown.x)
- + (e.getY() - mDown.y) * (e.getY() - mDown.y);
- }
-
- /**
- * enter a slice for a view
- * updates model only
- * @param item
- */
- private void onEnter(PieItem item) {
- if (mCurrentItem != null) {
- mCurrentItem.setSelected(false);
- }
- if (item != null && item.isEnabled()) {
- item.setSelected(true);
- mCurrentItem = item;
- if ((mCurrentItem != mOpenItem) && mCurrentItem.hasItems()) {
- openCurrentItem();
- }
- } else {
- mCurrentItem = null;
- }
- }
-
- private void deselect() {
- if (mCurrentItem != null) {
- mCurrentItem.setSelected(false);
- }
- if (mOpenItem != null) {
- mOpenItem = null;
- }
- mCurrentItem = null;
- }
-
- private void openCurrentItem() {
- if ((mCurrentItem != null) && mCurrentItem.hasItems()) {
- mCurrentItem.setSelected(false);
- mOpenItem = mCurrentItem;
- mOpening = true;
- mXFade = new LinearAnimation(1, 0);
- mXFade.setDuration(PIE_XFADE_DURATION);
- mXFade.setAnimationListener(new AnimationListener() {
- @Override
- public void onAnimationStart(Animation animation) {
- }
-
- @Override
- public void onAnimationEnd(Animation animation) {
- mXFade = null;
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) {
- }
- });
- mXFade.startNow();
- mOverlay.startAnimation(mXFade);
- }
- }
-
- private PointF getPolar(float x, float y, boolean useOffset) {
- PointF res = new PointF();
- // get angle and radius from x/y
- res.x = (float) Math.PI / 2;
- x = x - mCenter.x;
- y = mCenter.y - y;
- res.y = (float) Math.sqrt(x * x + y * y);
- if (x != 0) {
- res.x = (float) Math.atan2(y, x);
- if (res.x < 0) {
- res.x = (float) (2 * Math.PI + res.x);
- }
- }
- res.y = res.y + (useOffset ? mTouchOffset : 0);
- return res;
- }
-
- /**
- * @param polar x: angle, y: dist
- * @return the item at angle/dist or null
- */
- private PieItem findItem(PointF polar) {
- // find the matching item:
- List<PieItem> items = (mOpenItem != null) ? mOpenItem.getItems() : mItems;
- for (PieItem item : items) {
- if (inside(polar, item)) {
- return item;
- }
- }
- return null;
- }
-
- private boolean inside(PointF polar, PieItem item) {
- return (item.getInnerRadius() < polar.y)
- && (item.getStartAngle() < polar.x)
- && (item.getStartAngle() + item.getSweep() > polar.x)
- && (!mTapMode || (item.getOuterRadius() > polar.y));
- }
-
- @Override
- public boolean handlesTouch() {
- return true;
- }
-
- // focus specific code
-
- public void setBlockFocus(boolean blocked) {
- mBlockFocus = blocked;
- if (blocked) {
- clear();
- }
- }
-
- public void setFocus(int x, int y) {
- mFocusX = x;
- mFocusY = y;
- setCircle(mFocusX, mFocusY);
- }
-
- public void alignFocus(int x, int y) {
- mOverlay.removeCallbacks(mDisappear);
- mAnimation.cancel();
- mAnimation.reset();
- mFocusX = x;
- mFocusY = y;
- mDialAngle = DIAL_HORIZONTAL;
- setCircle(x, y);
- mFocused = false;
- }
-
- public int getSize() {
- return 2 * mCircleSize;
- }
-
- private int getRandomRange() {
- return (int) (-60 + 120 * Math.random());
- }
-
- @Override
- public void layout(int l, int t, int r, int b) {
- super.layout(l, t, r, b);
- mCenterX = (r - l) / 2;
- mCenterY = (b - t) / 2;
- mFocusX = mCenterX;
- mFocusY = mCenterY;
- setCircle(mFocusX, mFocusY);
- if (isVisible() && mState == STATE_PIE) {
- setCenter(mCenterX, mCenterY);
- layoutPie();
- }
- }
-
- private void setCircle(int cx, int cy) {
- mCircle.set(cx - mCircleSize, cy - mCircleSize,
- cx + mCircleSize, cy + mCircleSize);
- mDial.set(cx - mCircleSize + mInnerOffset, cy - mCircleSize + mInnerOffset,
- cx + mCircleSize - mInnerOffset, cy + mCircleSize - mInnerOffset);
- }
-
- public void drawFocus(Canvas canvas) {
- if (mBlockFocus) {
- return;
- }
- mFocusPaint.setStrokeWidth(mOuterStroke);
- canvas.drawCircle((float) mFocusX, (float) mFocusY, (float) mCircleSize, mFocusPaint);
- if (mState == STATE_PIE) {
- return;
- }
- int color = mFocusPaint.getColor();
- if (mState == STATE_FINISHING) {
- mFocusPaint.setColor(mFocused ? mSuccessColor : mFailColor);
- }
- mFocusPaint.setStrokeWidth(mInnerStroke);
- drawLine(canvas, mDialAngle, mFocusPaint);
- drawLine(canvas, mDialAngle + 45, mFocusPaint);
- drawLine(canvas, mDialAngle + 180, mFocusPaint);
- drawLine(canvas, mDialAngle + 225, mFocusPaint);
- canvas.save();
- // rotate the arc instead of its offset to better use framework's shape caching
- canvas.rotate(mDialAngle, mFocusX, mFocusY);
- canvas.drawArc(mDial, 0, 45, false, mFocusPaint);
- canvas.drawArc(mDial, 180, 45, false, mFocusPaint);
- canvas.restore();
- mFocusPaint.setColor(color);
- }
-
- private void drawLine(Canvas canvas, int angle, Paint p) {
- convertCart(angle, mCircleSize - mInnerOffset, mPoint1);
- convertCart(angle, mCircleSize - mInnerOffset + mInnerOffset / 3, mPoint2);
- canvas.drawLine(mPoint1.x + mFocusX, mPoint1.y + mFocusY,
- mPoint2.x + mFocusX, mPoint2.y + mFocusY, p);
- }
-
- private static void convertCart(int angle, int radius, Point out) {
- double a = 2 * Math.PI * (angle % 360) / 360;
- out.x = (int) (radius * Math.cos(a) + 0.5);
- out.y = (int) (radius * Math.sin(a) + 0.5);
- }
-
- @Override
- public void showStart() {
- if (mState == STATE_PIE) {
- return;
- }
- cancelFocus();
- mStartAnimationAngle = 67;
- int range = getRandomRange();
- startAnimation(SCALING_UP_TIME,
- false, mStartAnimationAngle, mStartAnimationAngle + range);
- mState = STATE_FOCUSING;
- }
-
- @Override
- public void showSuccess(boolean timeout) {
- if (mState == STATE_FOCUSING) {
- startAnimation(SCALING_DOWN_TIME,
- timeout, mStartAnimationAngle);
- mState = STATE_FINISHING;
- mFocused = true;
- }
- }
-
- @Override
- public void showFail(boolean timeout) {
- if (mState == STATE_FOCUSING) {
- startAnimation(SCALING_DOWN_TIME,
- timeout, mStartAnimationAngle);
- mState = STATE_FINISHING;
- mFocused = false;
- }
- }
-
- private void cancelFocus() {
- mFocusCancelled = true;
- mOverlay.removeCallbacks(mDisappear);
- if (mAnimation != null) {
- mAnimation.cancel();
- }
- mFocusCancelled = false;
- mFocused = false;
- mState = STATE_IDLE;
- }
-
- @Override
- public void clear() {
- if (mState == STATE_PIE) {
- return;
- }
- cancelFocus();
- mOverlay.post(mDisappear);
- }
-
- private void startAnimation(long duration, boolean timeout,
- float toScale) {
- startAnimation(duration, timeout, mDialAngle,
- toScale);
- }
-
- private void startAnimation(long duration, boolean timeout,
- float fromScale, float toScale) {
- setVisible(true);
- mAnimation.reset();
- mAnimation.setDuration(duration);
- mAnimation.setScale(fromScale, toScale);
- mAnimation.setAnimationListener(timeout ? mEndAction : null);
- mOverlay.startAnimation(mAnimation);
- update();
- }
-
- private class EndAction implements Animation.AnimationListener {
- @Override
- public void onAnimationEnd(Animation animation) {
- // Keep the focus indicator for some time.
- if (!mFocusCancelled) {
- mOverlay.postDelayed(mDisappear, DISAPPEAR_TIMEOUT);
- }
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) {
- }
-
- @Override
- public void onAnimationStart(Animation animation) {
- }
- }
-
- private class Disappear implements Runnable {
- @Override
- public void run() {
- if (mState == STATE_PIE) {
- return;
- }
- setVisible(false);
- mFocusX = mCenterX;
- mFocusY = mCenterY;
- mState = STATE_IDLE;
- setCircle(mFocusX, mFocusY);
- mFocused = false;
- }
- }
-
- private class ScaleAnimation extends Animation {
- private float mFrom = 1f;
- private float mTo = 1f;
-
- public ScaleAnimation() {
- setFillAfter(true);
- }
-
- public void setScale(float from, float to) {
- mFrom = from;
- mTo = to;
- }
-
- @Override
- protected void applyTransformation(float interpolatedTime, Transformation t) {
- mDialAngle = (int) (mFrom + (mTo - mFrom) * interpolatedTime);
- }
- }
-
-
- private class LinearAnimation extends Animation {
- private float mFrom;
- private float mTo;
- private float mValue;
-
- public LinearAnimation(float from, float to) {
- setFillAfter(true);
- setInterpolator(new LinearInterpolator());
- mFrom = from;
- mTo = to;
- }
-
- public float getValue() {
- return mValue;
- }
-
- @Override
- protected void applyTransformation(float interpolatedTime, Transformation t) {
- mValue = (mFrom + (mTo - mFrom) * interpolatedTime);
- }
- }
-
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/README.txt b/src/com/android/messaging/ui/mediapicker/camerafocus/README.txt
deleted file mode 100644
index ed4e783..0000000
--- a/src/com/android/messaging/ui/mediapicker/camerafocus/README.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-The files in this package were copied from the android-4.4.4_r1 branch of ASOP from the folders
-com/android/camera/ and com/android/camera/ui from files with the same name. Some modifications
-have been made to remove unneeded features and adjust to our needs. \ No newline at end of file
diff --git a/src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java b/src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java
deleted file mode 100644
index 95cddc4..0000000
--- a/src/com/android/messaging/ui/mediapicker/camerafocus/RenderOverlay.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * 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.mediapicker.camerafocus;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class RenderOverlay extends FrameLayout {
-
- interface Renderer {
-
- public boolean handlesTouch();
- public boolean onTouchEvent(MotionEvent evt);
- public void setOverlay(RenderOverlay overlay);
- public void layout(int left, int top, int right, int bottom);
- public void draw(Canvas canvas);
-
- }
-
- private RenderView mRenderView;
- private List<Renderer> mClients;
-
- // reverse list of touch clients
- private List<Renderer> mTouchClients;
- private int[] mPosition = new int[2];
-
- public RenderOverlay(Context context, AttributeSet attrs) {
- super(context, attrs);
- mRenderView = new RenderView(context);
- addView(mRenderView, new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT));
- mClients = new ArrayList<Renderer>(10);
- mTouchClients = new ArrayList<Renderer>(10);
- setWillNotDraw(false);
-
- addRenderer(new PieRenderer(context));
- }
-
- public PieRenderer getPieRenderer() {
- for (Renderer renderer : mClients) {
- if (renderer instanceof PieRenderer) {
- return (PieRenderer) renderer;
- }
- }
- return null;
- }
-
- public void addRenderer(Renderer renderer) {
- mClients.add(renderer);
- renderer.setOverlay(this);
- if (renderer.handlesTouch()) {
- mTouchClients.add(0, renderer);
- }
- renderer.layout(getLeft(), getTop(), getRight(), getBottom());
- }
-
- public void addRenderer(int pos, Renderer renderer) {
- mClients.add(pos, renderer);
- renderer.setOverlay(this);
- renderer.layout(getLeft(), getTop(), getRight(), getBottom());
- }
-
- public void remove(Renderer renderer) {
- mClients.remove(renderer);
- renderer.setOverlay(null);
- }
-
- public int getClientSize() {
- return mClients.size();
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent m) {
- return false;
- }
-
- public boolean directDispatchTouch(MotionEvent m, Renderer target) {
- mRenderView.setTouchTarget(target);
- boolean res = super.dispatchTouchEvent(m);
- mRenderView.setTouchTarget(null);
- return res;
- }
-
- private void adjustPosition() {
- getLocationInWindow(mPosition);
- }
-
- public int getWindowPositionX() {
- return mPosition[0];
- }
-
- public int getWindowPositionY() {
- return mPosition[1];
- }
-
- public void update() {
- mRenderView.invalidate();
- }
-
- private class RenderView extends View {
-
- private Renderer mTouchTarget;
-
- public RenderView(Context context) {
- super(context);
- setWillNotDraw(false);
- }
-
- public void setTouchTarget(Renderer target) {
- mTouchTarget = target;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent evt) {
- if (mTouchTarget != null) {
- return mTouchTarget.onTouchEvent(evt);
- }
- if (mTouchClients != null) {
- boolean res = false;
- for (Renderer client : mTouchClients) {
- res |= client.onTouchEvent(evt);
- }
- return res;
- }
- return false;
- }
-
- @Override
- public void onLayout(boolean changed, int left, int top, int right, int bottom) {
- adjustPosition();
- super.onLayout(changed, left, top, right, bottom);
- if (mClients == null) {
- return;
- }
- for (Renderer renderer : mClients) {
- renderer.layout(left, top, right, bottom);
- }
- }
-
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
- if (mClients == null) {
- return;
- }
- boolean redraw = false;
- for (Renderer renderer : mClients) {
- renderer.draw(canvas);
- redraw = redraw || ((OverlayRenderer) renderer).isVisible();
- }
- if (redraw) {
- invalidate();
- }
- }
- }
-
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoBitmapLoader.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoBitmapLoader.java
deleted file mode 100644
index d139a38..0000000
--- a/src/com/android/messaging/ui/photoviewer/BuglePhotoBitmapLoader.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * 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.photoviewer;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.support.rastermill.FrameSequenceDrawable;
-import android.support.v4.content.AsyncTaskLoader;
-
-import com.android.ex.photo.PhotoViewController;
-import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface;
-import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface.BitmapResult;
-import com.android.messaging.datamodel.media.ImageRequestDescriptor;
-import com.android.messaging.datamodel.media.ImageResource;
-import com.android.messaging.datamodel.media.MediaRequest;
-import com.android.messaging.datamodel.media.MediaResourceManager;
-import com.android.messaging.datamodel.media.UriImageRequestDescriptor;
-import com.android.messaging.util.ImageUtils;
-
-/**
- * Loader for the bitmap of a photo.
- */
-public class BuglePhotoBitmapLoader extends AsyncTaskLoader<BitmapResult>
- implements PhotoBitmapLoaderInterface {
- private String mPhotoUri;
- private ImageResource mImageResource;
- // The drawable that is currently "in use" and being presented to the user. This drawable
- // should never exist without the image resource backing it.
- private Drawable mDrawable;
-
- public BuglePhotoBitmapLoader(Context context, String photoUri) {
- super(context);
- mPhotoUri = photoUri;
- }
-
- @Override
- public void setPhotoUri(String photoUri) {
- mPhotoUri = photoUri;
- }
-
- @Override
- public BitmapResult loadInBackground() {
- final BitmapResult result = new BitmapResult();
- final Context context = getContext();
- if (context != null && mPhotoUri != null) {
- final ImageRequestDescriptor descriptor =
- new UriImageRequestDescriptor(Uri.parse(mPhotoUri),
- PhotoViewController.sMaxPhotoSize, PhotoViewController.sMaxPhotoSize,
- true /* allowCompression */, false /* isStatic */,
- false /* cropToCircle */,
- ImageUtils.DEFAULT_CIRCLE_BACKGROUND_COLOR /* circleBackgroundColor */,
- ImageUtils.DEFAULT_CIRCLE_STROKE_COLOR /* circleStrokeColor */);
- final MediaRequest<ImageResource> imageRequest =
- descriptor.buildSyncMediaRequest(context);
- final ImageResource imageResource =
- MediaResourceManager.get().requestMediaResourceSync(imageRequest);
- if (imageResource != null) {
- setImageResource(imageResource);
- result.status = BitmapResult.STATUS_SUCCESS;
- result.drawable = mImageResource.getDrawable(context.getResources());
- } else {
- releaseImageResource();
- result.status = BitmapResult.STATUS_EXCEPTION;
- }
- } else {
- result.status = BitmapResult.STATUS_EXCEPTION;
- }
- return result;
- }
-
- /**
- * Called when there is new data to deliver to the client. The super class will take care of
- * delivering it; the implementation here just adds a little more logic.
- */
- @Override
- public void deliverResult(BitmapResult result) {
- final Drawable drawable = result != null ? result.drawable : null;
- if (isReset()) {
- // An async query came in while the loader is stopped. We don't need the result.
- releaseDrawable(drawable);
- return;
- }
-
- // We are now going to display this drawable so set to mDrawable
- mDrawable = drawable;
-
- if (isStarted()) {
- // If the Loader is currently started, we can immediately deliver its results.
- super.deliverResult(result);
- }
- }
-
- /**
- * Handles a request to start the Loader.
- */
- @Override
- protected void onStartLoading() {
- if (mDrawable != null) {
- // If we currently have a result available, deliver it
- // immediately.
- final BitmapResult result = new BitmapResult();
- result.status = BitmapResult.STATUS_SUCCESS;
- result.drawable = mDrawable;
- deliverResult(result);
- }
-
- if (takeContentChanged() || (mImageResource == null)) {
- // If the data has changed since the last time it was loaded
- // or is not currently available, start a load.
- forceLoad();
- }
- }
-
- /**
- * Handles a request to stop the Loader.
- */
- @Override
- protected void onStopLoading() {
- // Attempt to cancel the current load task if possible.
- cancelLoad();
- }
-
- /**
- * Handles a request to cancel a load.
- */
- @Override
- public void onCanceled(BitmapResult result) {
- super.onCanceled(result);
-
- // At this point we can release the resources associated with 'drawable' if needed.
- if (result != null) {
- releaseDrawable(result.drawable);
- }
- }
-
- /**
- * Handles a request to completely reset the Loader.
- */
- @Override
- protected void onReset() {
- super.onReset();
-
- // Ensure the loader is stopped
- onStopLoading();
-
- releaseImageResource();
- }
-
- private void releaseDrawable(Drawable drawable) {
- if (drawable != null && drawable instanceof FrameSequenceDrawable
- && !((FrameSequenceDrawable) drawable).isDestroyed()) {
- ((FrameSequenceDrawable) drawable).destroy();
- }
-
- }
-
- private void setImageResource(final ImageResource resource) {
- if (mImageResource != resource) {
- // Clear out any information for what is currently used
- releaseImageResource();
- mImageResource = resource;
- // No need to add ref since a ref is already reserved as a result of
- // requestMediaResourceSync.
- }
- }
-
- private void releaseImageResource() {
- // If we are getting rid of the imageResource backing the drawable, we must also
- // destroy the drawable before releasing it.
- releaseDrawable(mDrawable);
- mDrawable = null;
-
- if (mImageResource != null) {
- mImageResource.release();
- }
- mImageResource = null;
- }
-} \ No newline at end of file
diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoPageAdapter.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoPageAdapter.java
deleted file mode 100644
index 52498c7..0000000
--- a/src/com/android/messaging/ui/photoviewer/BuglePhotoPageAdapter.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.photoviewer;
-
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.support.v4.app.FragmentManager;
-
-import com.android.ex.photo.adapters.PhotoPagerAdapter;
-import com.android.ex.photo.fragments.PhotoViewFragment;
-
-public class BuglePhotoPageAdapter extends PhotoPagerAdapter {
-
- public BuglePhotoPageAdapter(Context context, FragmentManager fm, Cursor c, float maxScale,
- boolean thumbsFullScreen) {
- super(context, fm, c, maxScale, thumbsFullScreen);
- }
-
- @Override
- protected PhotoViewFragment createPhotoViewFragment(Intent intent, int position,
- boolean onlyShowSpinner) {
- return BuglePhotoViewFragment.newInstance(intent, position, onlyShowSpinner);
- }
-}
diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewActivity.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoViewActivity.java
deleted file mode 100644
index 1924a96..0000000
--- a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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.photoviewer;
-
-import com.android.ex.photo.PhotoViewActivity;
-import com.android.ex.photo.PhotoViewController;
-
-/**
- * Activity to display the conversation images in full-screen. Most of the customization is in
- * {@link BuglePhotoViewController}.
- */
-public class BuglePhotoViewActivity extends PhotoViewActivity {
- @Override
- public PhotoViewController createController() {
- return new BuglePhotoViewController(this);
- }
-}
diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewController.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoViewController.java
deleted file mode 100644
index eb39886..0000000
--- a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewController.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * 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.photoviewer;
-
-import android.Manifest;
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.content.Loader;
-import android.text.TextUtils;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.widget.ShareActionProvider;
-import android.widget.Toast;
-
-import com.android.ex.photo.PhotoViewController;
-import com.android.ex.photo.adapters.PhotoPagerAdapter;
-import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface.BitmapResult;
-import com.android.messaging.R;
-import com.android.messaging.datamodel.ConversationImagePartsView.PhotoViewQuery;
-import com.android.messaging.datamodel.MediaScratchFileProvider;
-import com.android.messaging.ui.conversation.ConversationFragment;
-import com.android.messaging.util.Dates;
-import com.android.messaging.util.LogUtil;
-import com.android.messaging.util.OsUtil;
-
-/**
- * Customizations for the photoviewer to display conversation images in full screen.
- */
-public class BuglePhotoViewController extends PhotoViewController {
- private ShareActionProvider mShareActionProvider;
- private MenuItem mShareItem;
- private MenuItem mSaveItem;
-
- public BuglePhotoViewController(final ActivityInterface activity) {
- super(activity);
- }
-
- @Override
- public Loader<BitmapResult> onCreateBitmapLoader(
- final int id, final Bundle args, final String uri) {
- switch (id) {
- case BITMAP_LOADER_AVATAR:
- case BITMAP_LOADER_THUMBNAIL:
- case BITMAP_LOADER_PHOTO:
- return new BuglePhotoBitmapLoader(getActivity().getContext(), uri);
- default:
- LogUtil.e(LogUtil.BUGLE_TAG,
- "Photoviewer unable to open bitmap loader with unknown id: " + id);
- return null;
- }
- }
-
- @Override
- public void updateActionBar() {
- final Cursor cursor = getCursorAtProperPosition();
-
- if (mSaveItem == null || cursor == null) {
- // Load not finished, called from framework code before ready
- return;
- }
- // Show the name as the title
- mActionBarTitle = cursor.getString(PhotoViewQuery.INDEX_SENDER_FULL_NAME);
- if (TextUtils.isEmpty(mActionBarTitle)) {
- // If the name is not known, fall back to the phone number
- mActionBarTitle = cursor.getString(PhotoViewQuery.INDEX_DISPLAY_DESTINATION);
- }
-
- // Show the timestamp as the subtitle
- final long receivedTimestamp = cursor.getLong(PhotoViewQuery.INDEX_RECEIVED_TIMESTAMP);
- mActionBarSubtitle = Dates.getMessageTimeString(receivedTimestamp).toString();
-
- setActionBarTitles(getActivity().getActionBarInterface());
- mSaveItem.setVisible(!isTempFile());
-
- updateShareActionProvider();
- }
-
- private void updateShareActionProvider() {
- final PhotoPagerAdapter adapter = getAdapter();
- final Cursor cursor = getCursorAtProperPosition();
- if (mShareActionProvider == null || mShareItem == null || adapter == null ||
- cursor == null) {
- // Not enough stuff loaded to update the share action
- return;
- }
- final String photoUri = adapter.getPhotoUri(cursor);
- if (isTempFile()) {
- mShareItem.setVisible(false);
- return;
- }
- final String contentType = adapter.getContentType(cursor);
-
- final Intent shareIntent = new Intent();
- shareIntent.setAction(Intent.ACTION_SEND);
- shareIntent.setType(contentType);
- shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse(photoUri));
- mShareActionProvider.setShareIntent(shareIntent);
- mShareItem.setVisible(true);
- }
-
- /**
- * Checks whether the current photo is a temp file. A temp file can be deleted at any time, so
- * we need to disable share and save options because the file may no longer be there.
- */
- private boolean isTempFile() {
- final Cursor cursor = getCursorAtProperPosition();
- final Uri photoUri = Uri.parse(getAdapter().getPhotoUri(cursor));
- return MediaScratchFileProvider.isMediaScratchSpaceUri(photoUri);
- }
-
- @Override
- public boolean onCreateOptionsMenu(final Menu menu) {
- ((Activity) getActivity()).getMenuInflater().inflate(R.menu.photo_view_menu, menu);
-
- // Get the ShareActionProvider
- mShareItem = menu.findItem(R.id.action_share);
- mShareActionProvider = (ShareActionProvider) mShareItem.getActionProvider();
- updateShareActionProvider();
-
- mSaveItem = menu.findItem(R.id.action_save);
- return true;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(final Menu menu) {
- return !mIsEmpty;
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- if (item.getItemId() == R.id.action_save) {
- if (OsUtil.hasStoragePermission()) {
- final PhotoPagerAdapter adapter = getAdapter();
- final Cursor cursor = getCursorAtProperPosition();
- if (cursor == null) {
- final Context context = getActivity().getContext();
- final String error = context.getResources().getQuantityString(
- R.plurals.attachment_save_error, 1, 1);
- Toast.makeText(context, error, Toast.LENGTH_SHORT).show();
- return true;
- }
- final String photoUri = adapter.getPhotoUri(cursor);
- new ConversationFragment.SaveAttachmentTask(((Activity) getActivity()),
- Uri.parse(photoUri), adapter.getContentType(cursor)).executeOnThreadPool();
- } else {
- ((Activity)getActivity()).requestPermissions(
- new String[] { Manifest.permission.WRITE_EXTERNAL_STORAGE }, 0);
- }
- return true;
- } else {
- return super.onOptionsItemSelected(item);
- }
- }
-
- @Override
- public PhotoPagerAdapter createPhotoPagerAdapter(final Context context,
- final FragmentManager fm, final Cursor c, final float maxScale) {
- return new BuglePhotoPageAdapter(context, fm, c, maxScale, mDisplayThumbsFullScreen);
- }
-}
diff --git a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewFragment.java b/src/com/android/messaging/ui/photoviewer/BuglePhotoViewFragment.java
deleted file mode 100644
index 698c510..0000000
--- a/src/com/android/messaging/ui/photoviewer/BuglePhotoViewFragment.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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.photoviewer;
-
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.support.rastermill.FrameSequenceDrawable;
-import android.support.v4.content.Loader;
-
-import com.android.ex.photo.PhotoViewCallbacks;
-import com.android.ex.photo.fragments.PhotoViewFragment;
-import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface.BitmapResult;
-
-public class BuglePhotoViewFragment extends PhotoViewFragment {
-
- /** Public no-arg constructor for allowing the framework to handle orientation changes */
- public BuglePhotoViewFragment() {
- // Do nothing.
- }
-
- public static PhotoViewFragment newInstance(Intent intent, int position,
- boolean onlyShowSpinner) {
- final PhotoViewFragment f = new BuglePhotoViewFragment();
- initializeArguments(intent, position, onlyShowSpinner, f);
- return f;
- }
-
- @Override
- public void onLoadFinished(Loader<BitmapResult> loader, BitmapResult result) {
- super.onLoadFinished(loader, result);
- // Need to check for the first time when we load the photos
- if (PhotoViewCallbacks.BITMAP_LOADER_PHOTO == loader.getId()
- && result.status == BitmapResult.STATUS_SUCCESS
- && mCallback.isFragmentActive(this)) {
- startGif();
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
- startGif();
- }
-
- @Override
- public void onPause() {
- stopGif();
- super.onPause();
- }
-
- @Override
- public void onViewActivated() {
- super.onViewActivated();
- startGif();
- }
-
- @Override
- public void resetViews() {
- super.resetViews();
- stopGif();
- }
-
- private void stopGif() {
- final Drawable drawable = getDrawable();
- if (drawable != null && drawable instanceof FrameSequenceDrawable) {
- ((FrameSequenceDrawable) drawable).stop();
- }
- }
-
- private void startGif() {
- final Drawable drawable = getDrawable();
- if (drawable != null && drawable instanceof FrameSequenceDrawable) {
- ((FrameSequenceDrawable) drawable).start();
- }
- }
-}