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