| /* |
| * Copyright (C) 2016 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.dialer.callcomposer; |
| |
| import static android.app.Activity.RESULT_OK; |
| |
| import android.Manifest.permission; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Parcelable; |
| import android.provider.Settings; |
| import android.support.annotation.NonNull; |
| import android.support.annotation.Nullable; |
| import android.support.v4.app.LoaderManager.LoaderCallbacks; |
| import android.support.v4.content.CursorLoader; |
| import android.support.v4.content.Loader; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.View.OnClickListener; |
| import android.view.ViewGroup; |
| import android.widget.GridView; |
| import android.widget.ImageView; |
| import android.widget.TextView; |
| import com.android.dialer.common.LogUtil; |
| import com.android.dialer.common.concurrent.DialerExecutor; |
| import com.android.dialer.common.concurrent.DialerExecutorComponent; |
| import com.android.dialer.logging.DialerImpression; |
| import com.android.dialer.logging.Logger; |
| import com.android.dialer.theme.base.ThemeComponent; |
| import com.android.dialer.util.PermissionsUtil; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** Fragment used to compose call with image from the user's gallery. */ |
| public class GalleryComposerFragment extends CallComposerFragment |
| implements LoaderCallbacks<Cursor>, OnClickListener { |
| |
| private static final String SELECTED_DATA_KEY = "selected_data"; |
| private static final String IS_COPY_KEY = "is_copy"; |
| private static final String INSERTED_IMAGES_KEY = "inserted_images"; |
| |
| private static final int RESULT_LOAD_IMAGE = 1; |
| private static final int RESULT_OPEN_SETTINGS = 2; |
| |
| private GalleryGridAdapter adapter; |
| private GridView galleryGridView; |
| private View permissionView; |
| private View allowPermission; |
| |
| private String[] permissions = new String[] {permission.READ_EXTERNAL_STORAGE}; |
| private CursorLoader cursorLoader; |
| private GalleryGridItemData selectedData = null; |
| private boolean selectedDataIsCopy; |
| private List<GalleryGridItemData> insertedImages = new ArrayList<>(); |
| |
| private DialerExecutor<Uri> copyAndResizeImage; |
| |
| public static GalleryComposerFragment newInstance() { |
| return new GalleryComposerFragment(); |
| } |
| |
| @Nullable |
| @Override |
| public View onCreateView( |
| LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle bundle) { |
| View view = inflater.inflate(R.layout.fragment_gallery_composer, container, false); |
| galleryGridView = (GridView) view.findViewById(R.id.gallery_grid_view); |
| permissionView = view.findViewById(R.id.permission_view); |
| |
| if (!PermissionsUtil.hasPermission(getContext(), permission.READ_EXTERNAL_STORAGE)) { |
| Logger.get(getContext()).logImpression(DialerImpression.Type.STORAGE_PERMISSION_DISPLAYED); |
| LogUtil.i("GalleryComposerFragment.onCreateView", "Permission view shown."); |
| ImageView permissionImage = (ImageView) permissionView.findViewById(R.id.permission_icon); |
| TextView permissionText = (TextView) permissionView.findViewById(R.id.permission_text); |
| allowPermission = permissionView.findViewById(R.id.allow); |
| |
| allowPermission.setOnClickListener(this); |
| permissionText.setText(R.string.gallery_permission_text); |
| permissionImage.setImageResource(R.drawable.quantum_ic_photo_white_48); |
| permissionImage.setColorFilter(ThemeComponent.get(getContext()).theme().getColorPrimary()); |
| permissionView.setVisibility(View.VISIBLE); |
| } else { |
| if (bundle != null) { |
| selectedData = bundle.getParcelable(SELECTED_DATA_KEY); |
| selectedDataIsCopy = bundle.getBoolean(IS_COPY_KEY); |
| insertedImages = bundle.getParcelableArrayList(INSERTED_IMAGES_KEY); |
| } |
| setupGallery(); |
| } |
| return view; |
| } |
| |
| @Override |
| public void onActivityCreated(@Nullable Bundle bundle) { |
| super.onActivityCreated(bundle); |
| |
| copyAndResizeImage = |
| DialerExecutorComponent.get(getContext()) |
| .dialerExecutorFactory() |
| .createUiTaskBuilder( |
| getActivity().getFragmentManager(), |
| "copyAndResizeImage", |
| new CopyAndResizeImageWorker(getActivity().getApplicationContext())) |
| .onSuccess( |
| output -> { |
| GalleryGridItemData data1 = |
| adapter.insertEntry(output.first.getAbsolutePath(), output.second); |
| insertedImages.add(0, data1); |
| setSelected(data1, true); |
| }) |
| .onFailure( |
| throwable -> { |
| // TODO(a bug) - gracefully handle message failure |
| LogUtil.e( |
| "GalleryComposerFragment.onFailure", "data preparation failed", throwable); |
| }) |
| .build(); |
| } |
| |
| private void setupGallery() { |
| adapter = new GalleryGridAdapter(getContext(), null, this); |
| galleryGridView.setAdapter(adapter); |
| getLoaderManager().initLoader(0 /* id */, null /* args */, this /* loaderCallbacks */); |
| } |
| |
| @Override |
| public Loader<Cursor> onCreateLoader(int id, Bundle args) { |
| return cursorLoader = new GalleryCursorLoader(getContext()); |
| } |
| |
| @Override |
| public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { |
| adapter.swapCursor(cursor); |
| if (insertedImages != null && !insertedImages.isEmpty()) { |
| adapter.insertEntries(insertedImages); |
| } |
| setSelected(selectedData, selectedDataIsCopy); |
| } |
| |
| @Override |
| public void onLoaderReset(Loader<Cursor> loader) { |
| adapter.swapCursor(null); |
| } |
| |
| @Override |
| public void onClick(View view) { |
| if (view == allowPermission) { |
| // Checks to see if the user has permanently denied this permission. If this is their first |
| // time seeing this permission or they've only pressed deny previously, they will see the |
| // permission request. If they've permanently denied the permission, they will be sent to |
| // Dialer settings in order to enable the permission. |
| if (PermissionsUtil.isFirstRequest(getContext(), permissions[0]) |
| || shouldShowRequestPermissionRationale(permissions[0])) { |
| LogUtil.i("GalleryComposerFragment.onClick", "Storage permission requested."); |
| Logger.get(getContext()).logImpression(DialerImpression.Type.STORAGE_PERMISSION_REQUESTED); |
| requestPermissions(permissions, STORAGE_PERMISSION); |
| } else { |
| LogUtil.i("GalleryComposerFragment.onClick", "Settings opened to enable permission."); |
| Logger.get(getContext()).logImpression(DialerImpression.Type.STORAGE_PERMISSION_SETTINGS); |
| Intent intent = new Intent(Intent.ACTION_VIEW); |
| intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); |
| intent.setData(Uri.parse("package:" + getContext().getPackageName())); |
| startActivityForResult(intent, RESULT_OPEN_SETTINGS); |
| } |
| return; |
| } else { |
| GalleryGridItemView itemView = ((GalleryGridItemView) view); |
| if (itemView.isGallery()) { |
| Intent intent = new Intent(Intent.ACTION_GET_CONTENT); |
| intent.setType("image/*"); |
| intent.putExtra(Intent.EXTRA_MIME_TYPES, GalleryCursorLoader.ACCEPTABLE_IMAGE_TYPES); |
| intent.addCategory(Intent.CATEGORY_OPENABLE); |
| startActivityForResult(intent, RESULT_LOAD_IMAGE); |
| } else if (itemView.getData().equals(selectedData)) { |
| clearComposer(); |
| } else { |
| setSelected(new GalleryGridItemData(itemView.getData()), false); |
| } |
| } |
| } |
| |
| @Nullable |
| public GalleryGridItemData getGalleryData() { |
| return selectedData; |
| } |
| |
| public GridView getGalleryGridView() { |
| return galleryGridView; |
| } |
| |
| @Override |
| public void onActivityResult(int requestCode, int resultCode, Intent data) { |
| super.onActivityResult(requestCode, resultCode, data); |
| if (requestCode == RESULT_LOAD_IMAGE && resultCode == RESULT_OK && data != null) { |
| prepareDataForAttachment(data); |
| } else if (requestCode == RESULT_OPEN_SETTINGS |
| && PermissionsUtil.hasPermission(getContext(), permission.READ_EXTERNAL_STORAGE)) { |
| permissionView.setVisibility(View.GONE); |
| setupGallery(); |
| } |
| } |
| |
| private void setSelected(GalleryGridItemData data, boolean isCopy) { |
| selectedData = data; |
| selectedDataIsCopy = isCopy; |
| adapter.setSelected(selectedData); |
| CallComposerListener listener = getListener(); |
| if (listener != null) { |
| getListener().composeCall(this); |
| } |
| } |
| |
| @Override |
| public boolean shouldHide() { |
| return selectedData == null |
| || selectedData.getFilePath() == null |
| || selectedData.getMimeType() == null; |
| } |
| |
| @Override |
| public void clearComposer() { |
| setSelected(null, false); |
| } |
| |
| @Override |
| public void onSaveInstanceState(Bundle outState) { |
| super.onSaveInstanceState(outState); |
| outState.putParcelable(SELECTED_DATA_KEY, selectedData); |
| outState.putBoolean(IS_COPY_KEY, selectedDataIsCopy); |
| outState.putParcelableArrayList( |
| INSERTED_IMAGES_KEY, (ArrayList<? extends Parcelable>) insertedImages); |
| } |
| |
| @Override |
| public void onRequestPermissionsResult( |
| int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { |
| if (permissions.length > 0 && permissions[0].equals(this.permissions[0])) { |
| PermissionsUtil.permissionRequested(getContext(), permissions[0]); |
| } |
| if (requestCode == STORAGE_PERMISSION |
| && grantResults.length > 0 |
| && grantResults[0] == PackageManager.PERMISSION_GRANTED) { |
| Logger.get(getContext()).logImpression(DialerImpression.Type.STORAGE_PERMISSION_GRANTED); |
| LogUtil.i("GalleryComposerFragment.onRequestPermissionsResult", "Permission granted."); |
| permissionView.setVisibility(View.GONE); |
| setupGallery(); |
| } else if (requestCode == STORAGE_PERMISSION) { |
| Logger.get(getContext()).logImpression(DialerImpression.Type.STORAGE_PERMISSION_DENIED); |
| LogUtil.i("GalleryComposerFragment.onRequestPermissionsResult", "Permission denied."); |
| } |
| } |
| |
| public CursorLoader getCursorLoader() { |
| return cursorLoader; |
| } |
| |
| public boolean selectedDataIsCopy() { |
| return selectedDataIsCopy; |
| } |
| |
| private void prepareDataForAttachment(Intent data) { |
| // We're using the builtin photo picker which supplies the return url as it's "data". |
| String url = data.getDataString(); |
| if (url == null) { |
| final Bundle extras = data.getExtras(); |
| if (extras != null) { |
| final Uri uri = extras.getParcelable(Intent.EXTRA_STREAM); |
| if (uri != null) { |
| url = uri.toString(); |
| } |
| } |
| } |
| |
| // This should never happen, but just in case.. |
| // Guard against null uri cases for when the activity returns a null/invalid intent. |
| if (url != null) { |
| copyAndResizeImage.executeParallel(Uri.parse(url)); |
| } else { |
| // TODO(a bug) - gracefully handle message failure |
| } |
| } |
| } |