1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.messaging.ui.mediapicker; 18 19 import android.Manifest; 20 import android.app.Activity; 21 import android.content.Intent; 22 import android.content.pm.PackageManager; 23 import android.database.Cursor; 24 import android.database.MatrixCursor; 25 import android.database.MergeCursor; 26 import androidx.appcompat.app.ActionBar; 27 import android.view.LayoutInflater; 28 import android.view.Menu; 29 import android.view.MenuInflater; 30 import android.view.MenuItem; 31 import android.view.View; 32 import android.view.ViewGroup; 33 34 import com.android.messaging.Factory; 35 import com.android.messaging.R; 36 import com.android.messaging.datamodel.data.GalleryGridItemData; 37 import com.android.messaging.datamodel.data.MediaPickerData; 38 import com.android.messaging.datamodel.data.MessagePartData; 39 import com.android.messaging.datamodel.data.MediaPickerData.MediaPickerDataListener; 40 import com.android.messaging.datamodel.data.PendingAttachmentData; 41 import com.android.messaging.ui.UIIntents; 42 import com.android.messaging.ui.mediapicker.DocumentImagePicker.SelectionListener; 43 import com.android.messaging.util.Assert; 44 import com.android.messaging.util.OsUtil; 45 46 /** 47 * Chooser which allows the user to select one or more existing images or videos or audios. 48 */ 49 class GalleryMediaChooser extends MediaChooser implements 50 GalleryGridView.GalleryGridViewListener, MediaPickerDataListener { 51 private final GalleryGridAdapter mAdapter; 52 private GalleryGridView mGalleryGridView; 53 private View mMissingPermissionView; 54 55 /** Handles picking a media from the document picker. */ 56 private DocumentImagePicker mDocumentImagePicker; 57 GalleryMediaChooser(final MediaPicker mediaPicker)58 GalleryMediaChooser(final MediaPicker mediaPicker) { 59 super(mediaPicker); 60 mAdapter = new GalleryGridAdapter(Factory.get().getApplicationContext(), null); 61 mDocumentImagePicker = new DocumentImagePicker(mMediaPicker, 62 new SelectionListener() { 63 @Override 64 public void onDocumentSelected(final PendingAttachmentData data) { 65 if (mBindingRef.isBound()) { 66 mMediaPicker.dispatchPendingItemAdded(data); 67 } 68 } 69 }); 70 } 71 72 @Override getSupportedMediaTypes()73 public int getSupportedMediaTypes() { 74 return (MediaPicker.MEDIA_TYPE_IMAGE 75 | MediaPicker.MEDIA_TYPE_VIDEO 76 | MediaPicker.MEDIA_TYPE_AUDIO); 77 } 78 79 @Override destroyView()80 public View destroyView() { 81 mGalleryGridView.setAdapter(null); 82 mAdapter.setHostInterface(null); 83 // The loader is started only if startMediaPickerDataLoader() is called 84 if (OsUtil.hasStoragePermission()) { 85 mBindingRef.getData().destroyLoader(MediaPickerData.GALLERY_MEDIA_LOADER); 86 } 87 return super.destroyView(); 88 } 89 90 @Override getIconResource()91 public int getIconResource() { 92 return R.drawable.ic_image_light; 93 } 94 95 @Override getIconDescriptionResource()96 public int getIconDescriptionResource() { 97 return R.string.mediapicker_galleryChooserDescription; 98 } 99 100 @Override canSwipeDown()101 public boolean canSwipeDown() { 102 return mGalleryGridView.canSwipeDown(); 103 } 104 105 @Override onItemSelected(final MessagePartData item)106 public void onItemSelected(final MessagePartData item) { 107 mMediaPicker.dispatchItemsSelected(item, !mGalleryGridView.isMultiSelectEnabled()); 108 } 109 110 @Override onItemUnselected(final MessagePartData item)111 public void onItemUnselected(final MessagePartData item) { 112 mMediaPicker.dispatchItemUnselected(item); 113 } 114 115 @Override onConfirmSelection()116 public void onConfirmSelection() { 117 // The user may only confirm if multiselect is enabled. 118 Assert.isTrue(mGalleryGridView.isMultiSelectEnabled()); 119 mMediaPicker.dispatchConfirmItemSelection(); 120 } 121 122 @Override onUpdate()123 public void onUpdate() { 124 mMediaPicker.invalidateOptionsMenu(); 125 } 126 127 @Override onCreateOptionsMenu(final MenuInflater inflater, final Menu menu)128 public void onCreateOptionsMenu(final MenuInflater inflater, final Menu menu) { 129 if (mView != null) { 130 mGalleryGridView.onCreateOptionsMenu(inflater, menu); 131 } 132 } 133 134 @Override onOptionsItemSelected(final MenuItem item)135 public boolean onOptionsItemSelected(final MenuItem item) { 136 return (mView != null) ? mGalleryGridView.onOptionsItemSelected(item) : false; 137 } 138 139 @Override createView(final ViewGroup container)140 protected View createView(final ViewGroup container) { 141 final LayoutInflater inflater = getLayoutInflater(); 142 final View view = inflater.inflate( 143 R.layout.mediapicker_gallery_chooser, 144 container /* root */, 145 false /* attachToRoot */); 146 147 mGalleryGridView = (GalleryGridView) view.findViewById(R.id.gallery_grid_view); 148 mAdapter.setHostInterface(mGalleryGridView); 149 mGalleryGridView.setAdapter(mAdapter); 150 mGalleryGridView.setHostInterface(this); 151 mGalleryGridView.setDraftMessageDataModel(mMediaPicker.getDraftMessageDataModel()); 152 if (OsUtil.hasStoragePermission()) { 153 startMediaPickerDataLoader(); 154 } 155 156 mMissingPermissionView = view.findViewById(R.id.missing_permission_view); 157 updateForPermissionState(OsUtil.hasStoragePermission()); 158 return view; 159 } 160 161 @Override getActionBarTitleResId()162 int getActionBarTitleResId() { 163 return R.string.mediapicker_gallery_title; 164 } 165 166 @Override onDocumentPickerItemClicked()167 public void onDocumentPickerItemClicked() { 168 // Launch an external picker to pick item from document picker as attachment. 169 mDocumentImagePicker.launchPicker(); 170 } 171 172 @Override updateActionBar(final ActionBar actionBar)173 void updateActionBar(final ActionBar actionBar) { 174 super.updateActionBar(actionBar); 175 if (mGalleryGridView == null) { 176 return; 177 } 178 final int selectionCount = mGalleryGridView.getSelectionCount(); 179 if (selectionCount > 0 && mGalleryGridView.isMultiSelectEnabled()) { 180 actionBar.setTitle(getContext().getResources().getString( 181 R.string.mediapicker_gallery_title_selection, 182 selectionCount)); 183 } 184 } 185 186 @Override onMediaPickerDataUpdated(final MediaPickerData mediaPickerData, final Object data, final int loaderId)187 public void onMediaPickerDataUpdated(final MediaPickerData mediaPickerData, final Object data, 188 final int loaderId) { 189 mBindingRef.ensureBound(mediaPickerData); 190 Assert.equals(MediaPickerData.GALLERY_MEDIA_LOADER, loaderId); 191 Cursor rawCursor = null; 192 if (data instanceof Cursor) { 193 rawCursor = (Cursor) data; 194 } 195 // Before delivering the cursor, wrap around the local gallery cursor 196 // with an extra item for document picker integration in the front. 197 final MatrixCursor specialItemsCursor = 198 new MatrixCursor(GalleryGridItemData.SPECIAL_ITEM_COLUMNS); 199 specialItemsCursor.addRow(new Object[] { GalleryGridItemData.ID_DOCUMENT_PICKER_ITEM }); 200 final MergeCursor cursor = 201 new MergeCursor(new Cursor[] { specialItemsCursor, rawCursor }); 202 mAdapter.swapCursor(cursor); 203 } 204 205 @Override onResume()206 public void onResume() { 207 if (OsUtil.hasStoragePermission()) { 208 // Work around a bug in MediaStore where cursors querying the Files provider don't get 209 // updated for changes to Images.Media or Video.Media. 210 startMediaPickerDataLoader(); 211 } 212 } 213 214 @Override setSelected(final boolean selected)215 protected void setSelected(final boolean selected) { 216 super.setSelected(selected); 217 if (selected && !OsUtil.hasStoragePermission()) { 218 mMediaPicker.requestPermissions( 219 new String[] { Manifest.permission.READ_EXTERNAL_STORAGE }, 220 MediaPicker.GALLERY_PERMISSION_REQUEST_CODE); 221 } 222 } 223 startMediaPickerDataLoader()224 private void startMediaPickerDataLoader() { 225 mBindingRef 226 .getData() 227 .startLoader(MediaPickerData.GALLERY_MEDIA_LOADER, mBindingRef, null, this); 228 } 229 230 @Override onRequestPermissionsResult( final int requestCode, final String permissions[], final int[] grantResults)231 protected void onRequestPermissionsResult( 232 final int requestCode, final String permissions[], final int[] grantResults) { 233 if (requestCode == MediaPicker.GALLERY_PERMISSION_REQUEST_CODE) { 234 final boolean permissionGranted = grantResults[0] == PackageManager.PERMISSION_GRANTED; 235 if (permissionGranted) { 236 startMediaPickerDataLoader(); 237 } 238 updateForPermissionState(permissionGranted); 239 } 240 } 241 updateForPermissionState(final boolean granted)242 private void updateForPermissionState(final boolean granted) { 243 // onRequestPermissionsResult can sometimes get called before createView(). 244 if (mGalleryGridView == null) { 245 return; 246 } 247 248 mGalleryGridView.setVisibility(granted ? View.VISIBLE : View.GONE); 249 mMissingPermissionView.setVisibility(granted ? View.GONE : View.VISIBLE); 250 } 251 252 @Override onActivityResult( final int requestCode, final int resultCode, final Intent data)253 protected void onActivityResult( 254 final int requestCode, final int resultCode, final Intent data) { 255 if (requestCode == UIIntents.REQUEST_PICK_MEDIA_FROM_DOCUMENT_PICKER 256 && resultCode == Activity.RESULT_OK) { 257 mDocumentImagePicker.onActivityResult(requestCode, resultCode, data); 258 } 259 } 260 } 261