GalleryComposerFragment.java revision fc37b02f5d3381a7882770941e461b13b679b6ef
1/* 2 * Copyright (C) 2016 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 17package com.android.dialer.callcomposer; 18 19import static android.app.Activity.RESULT_OK; 20 21import android.Manifest.permission; 22import android.content.Intent; 23import android.content.pm.PackageManager; 24import android.database.Cursor; 25import android.net.Uri; 26import android.os.Bundle; 27import android.os.Parcelable; 28import android.provider.Settings; 29import android.support.annotation.NonNull; 30import android.support.annotation.Nullable; 31import android.support.v4.app.LoaderManager.LoaderCallbacks; 32import android.support.v4.content.ContextCompat; 33import android.support.v4.content.CursorLoader; 34import android.support.v4.content.Loader; 35import android.view.LayoutInflater; 36import android.view.View; 37import android.view.View.OnClickListener; 38import android.view.ViewGroup; 39import android.widget.GridView; 40import android.widget.ImageView; 41import android.widget.TextView; 42import com.android.dialer.callcomposer.util.CopyAndResizeImageTask; 43import com.android.dialer.callcomposer.util.CopyAndResizeImageTask.Callback; 44import com.android.dialer.common.LogUtil; 45import com.android.dialer.logging.Logger; 46import com.android.dialer.logging.nano.DialerImpression; 47import com.android.dialer.util.PermissionsUtil; 48import java.io.File; 49import java.util.ArrayList; 50import java.util.List; 51 52/** Fragment used to compose call with image from the user's gallery. */ 53public class GalleryComposerFragment extends CallComposerFragment 54 implements LoaderCallbacks<Cursor>, OnClickListener { 55 56 private static final String SELECTED_DATA_KEY = "selected_data"; 57 private static final String IS_COPY_KEY = "is_copy"; 58 private static final String INSERTED_IMAGES_KEY = "inserted_images"; 59 60 private static final int RESULT_LOAD_IMAGE = 1; 61 private static final int RESULT_OPEN_SETTINGS = 2; 62 63 private GalleryGridAdapter adapter; 64 private GridView galleryGridView; 65 private View permissionView; 66 private View allowPermission; 67 68 private String[] permissions = new String[] {permission.READ_EXTERNAL_STORAGE}; 69 private CursorLoader cursorLoader; 70 private GalleryGridItemData selectedData = null; 71 private boolean selectedDataIsCopy; 72 private List<GalleryGridItemData> insertedImages = new ArrayList<>(); 73 74 public static GalleryComposerFragment newInstance() { 75 return new GalleryComposerFragment(); 76 } 77 78 @Nullable 79 @Override 80 public View onCreateView( 81 LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle bundle) { 82 View view = inflater.inflate(R.layout.fragment_gallery_composer, container, false); 83 galleryGridView = (GridView) view.findViewById(R.id.gallery_grid_view); 84 permissionView = view.findViewById(R.id.permission_view); 85 86 if (!PermissionsUtil.hasPermission(getContext(), permission.READ_EXTERNAL_STORAGE)) { 87 Logger.get(getContext()).logImpression(DialerImpression.Type.STORAGE_PERMISSION_DISPLAYED); 88 LogUtil.i("GalleryComposerFragment.onCreateView", "Permission view shown."); 89 ImageView permissionImage = (ImageView) permissionView.findViewById(R.id.permission_icon); 90 TextView permissionText = (TextView) permissionView.findViewById(R.id.permission_text); 91 allowPermission = permissionView.findViewById(R.id.allow); 92 93 allowPermission.setOnClickListener(this); 94 permissionText.setText(R.string.gallery_permission_text); 95 permissionImage.setImageResource(R.drawable.quantum_ic_photo_white_48); 96 permissionImage.setColorFilter( 97 ContextCompat.getColor(getContext(), R.color.dialer_theme_color)); 98 permissionView.setVisibility(View.VISIBLE); 99 } else { 100 if (bundle != null) { 101 selectedData = bundle.getParcelable(SELECTED_DATA_KEY); 102 selectedDataIsCopy = bundle.getBoolean(IS_COPY_KEY); 103 insertedImages = bundle.getParcelableArrayList(INSERTED_IMAGES_KEY); 104 } 105 setupGallery(); 106 } 107 return view; 108 } 109 110 private void setupGallery() { 111 adapter = new GalleryGridAdapter(getContext(), null, this); 112 galleryGridView.setAdapter(adapter); 113 getLoaderManager().initLoader(0 /* id */, null /* args */, this /* loaderCallbacks */); 114 } 115 116 @Override 117 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 118 return cursorLoader = new GalleryCursorLoader(getContext()); 119 } 120 121 @Override 122 public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { 123 adapter.swapCursor(cursor); 124 if (insertedImages != null && !insertedImages.isEmpty()) { 125 adapter.insertEntries(insertedImages); 126 } 127 setSelected(selectedData, selectedDataIsCopy); 128 } 129 130 @Override 131 public void onLoaderReset(Loader<Cursor> loader) { 132 adapter.swapCursor(null); 133 } 134 135 @Override 136 public void onClick(View view) { 137 if (view == allowPermission) { 138 // Checks to see if the user has permanently denied this permission. If this is their first 139 // time seeing this permission or they've only pressed deny previously, they will see the 140 // permission request. If they've permanently denied the permission, they will be sent to 141 // Dialer settings in order to enable the permission. 142 if (PermissionsUtil.isFirstRequest(getContext(), permissions[0]) 143 || shouldShowRequestPermissionRationale(permissions[0])) { 144 LogUtil.i("GalleryComposerFragment.onClick", "Storage permission requested."); 145 Logger.get(getContext()).logImpression(DialerImpression.Type.STORAGE_PERMISSION_REQUESTED); 146 requestPermissions(permissions, STORAGE_PERMISSION); 147 } else { 148 LogUtil.i("GalleryComposerFragment.onClick", "Settings opened to enable permission."); 149 Logger.get(getContext()).logImpression(DialerImpression.Type.STORAGE_PERMISSION_SETTINGS); 150 Intent intent = new Intent(Intent.ACTION_VIEW); 151 intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 152 intent.setData(Uri.parse("package:" + getContext().getPackageName())); 153 startActivityForResult(intent, RESULT_OPEN_SETTINGS); 154 } 155 return; 156 } else { 157 GalleryGridItemView itemView = ((GalleryGridItemView) view); 158 if (itemView.isGallery()) { 159 Intent intent = new Intent(Intent.ACTION_GET_CONTENT); 160 intent.setType("image/*"); 161 intent.putExtra(Intent.EXTRA_MIME_TYPES, GalleryCursorLoader.ACCEPTABLE_IMAGE_TYPES); 162 intent.addCategory(Intent.CATEGORY_OPENABLE); 163 startActivityForResult(intent, RESULT_LOAD_IMAGE); 164 } else if (itemView.getData().equals(selectedData)) { 165 clearComposer(); 166 } else { 167 setSelected(new GalleryGridItemData(itemView.getData()), false); 168 } 169 } 170 } 171 172 @Nullable 173 public GalleryGridItemData getGalleryData() { 174 return selectedData; 175 } 176 177 public GridView getGalleryGridView() { 178 return galleryGridView; 179 } 180 181 @Override 182 public void onActivityResult(int requestCode, int resultCode, Intent data) { 183 super.onActivityResult(requestCode, resultCode, data); 184 if (requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && data != null) { 185 prepareDataForAttachment(data); 186 } else if (requestCode == RESULT_OPEN_SETTINGS 187 && PermissionsUtil.hasPermission(getContext(), permission.READ_EXTERNAL_STORAGE)) { 188 permissionView.setVisibility(View.GONE); 189 setupGallery(); 190 } 191 } 192 193 private void setSelected(GalleryGridItemData data, boolean isCopy) { 194 selectedData = data; 195 selectedDataIsCopy = isCopy; 196 adapter.setSelected(selectedData); 197 CallComposerListener listener = getListener(); 198 if (listener != null) { 199 getListener().composeCall(this); 200 } 201 } 202 203 @Override 204 public boolean shouldHide() { 205 return selectedData == null 206 || selectedData.getFilePath() == null 207 || selectedData.getMimeType() == null; 208 } 209 210 @Override 211 public void clearComposer() { 212 setSelected(null, false); 213 } 214 215 @Override 216 public void onSaveInstanceState(Bundle outState) { 217 super.onSaveInstanceState(outState); 218 outState.putParcelable(SELECTED_DATA_KEY, selectedData); 219 outState.putBoolean(IS_COPY_KEY, selectedDataIsCopy); 220 outState.putParcelableArrayList( 221 INSERTED_IMAGES_KEY, (ArrayList<? extends Parcelable>) insertedImages); 222 } 223 224 @Override 225 public void onRequestPermissionsResult( 226 int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 227 if (permissions.length > 0 && permissions[0].equals(this.permissions[0])) { 228 PermissionsUtil.permissionRequested(getContext(), permissions[0]); 229 } 230 if (requestCode == STORAGE_PERMISSION 231 && grantResults.length > 0 232 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 233 Logger.get(getContext()).logImpression(DialerImpression.Type.STORAGE_PERMISSION_GRANTED); 234 LogUtil.i("GalleryComposerFragment.onRequestPermissionsResult", "Permission granted."); 235 permissionView.setVisibility(View.GONE); 236 setupGallery(); 237 } else if (requestCode == STORAGE_PERMISSION) { 238 Logger.get(getContext()).logImpression(DialerImpression.Type.STORAGE_PERMISSION_DENIED); 239 LogUtil.i("GalleryComposerFragment.onRequestPermissionsResult", "Permission denied."); 240 } 241 } 242 243 public CursorLoader getCursorLoader() { 244 return cursorLoader; 245 } 246 247 public boolean selectedDataIsCopy() { 248 return selectedDataIsCopy; 249 } 250 251 private void prepareDataForAttachment(Intent data) { 252 // We're using the builtin photo picker which supplies the return url as it's "data". 253 String url = data.getDataString(); 254 if (url == null) { 255 final Bundle extras = data.getExtras(); 256 if (extras != null) { 257 final Uri uri = extras.getParcelable(Intent.EXTRA_STREAM); 258 if (uri != null) { 259 url = uri.toString(); 260 } 261 } 262 } 263 264 // This should never happen, but just in case.. 265 // Guard against null uri cases for when the activity returns a null/invalid intent. 266 if (url != null) { 267 new CopyAndResizeImageTask( 268 getContext(), 269 Uri.parse(url), 270 new Callback() { 271 @Override 272 public void onCopySuccessful(File file, String mimeType) { 273 GalleryGridItemData data = adapter.insertEntry(file.getAbsolutePath(), mimeType); 274 insertedImages.add(0, data); 275 setSelected(data, true); 276 } 277 278 @Override 279 public void onCopyFailed(Throwable throwable) { 280 // TODO(b/34279096) - gracefully handle message failure 281 LogUtil.e( 282 "GalleryComposerFragment.onFailure", "Data preparation failed", throwable); 283 } 284 }) 285 .execute(); 286 } else { 287 // TODO(b/34279096) - gracefully handle message failure 288 } 289 } 290} 291