diff options
Diffstat (limited to 'java/com/android/dialer/callcomposer/GalleryComposerFragment.java')
-rw-r--r-- | java/com/android/dialer/callcomposer/GalleryComposerFragment.java | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/java/com/android/dialer/callcomposer/GalleryComposerFragment.java b/java/com/android/dialer/callcomposer/GalleryComposerFragment.java new file mode 100644 index 000000000..01e067440 --- /dev/null +++ b/java/com/android/dialer/callcomposer/GalleryComposerFragment.java @@ -0,0 +1,309 @@ +/* + * 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.annotation.VisibleForTesting; +import android.support.v4.app.LoaderManager.LoaderCallbacks; +import android.support.v4.content.ContextCompat; +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.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DefaultDialerExecutorFactory; +import com.android.dialer.common.concurrent.DialerExecutor; +import com.android.dialer.common.concurrent.DialerExecutorFactory; +import com.android.dialer.logging.DialerImpression; +import com.android.dialer.logging.Logger; +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 DialerExecutorFactory executorFactory = new DefaultDialerExecutorFactory(); + + 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(); + } + + @VisibleForTesting + void setExecutorFactory(@NonNull DialerExecutorFactory executorFactory) { + this.executorFactory = Assert.isNotNull(executorFactory); + } + + @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( + ContextCompat.getColor(getContext(), R.color.dialer_theme_color)); + 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 = + executorFactory + .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(b/34279096) - 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(b/34279096) - gracefully handle message failure + } + } +} |