• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
newInstance()75   public static GalleryComposerFragment newInstance() {
76     return new GalleryComposerFragment();
77   }
78 
79   @Nullable
80   @Override
onCreateView( LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle bundle)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
onActivityCreated(@ullable Bundle bundle)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 
setupGallery()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
onCreateLoader(int id, Bundle args)145   public Loader<Cursor> onCreateLoader(int id, Bundle args) {
146     return cursorLoader = new GalleryCursorLoader(getContext());
147   }
148 
149   @Override
onLoadFinished(Loader<Cursor> loader, Cursor cursor)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
onLoaderReset(Loader<Cursor> loader)159   public void onLoaderReset(Loader<Cursor> loader) {
160     adapter.swapCursor(null);
161   }
162 
163   @Override
onClick(View view)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
getGalleryData()201   public GalleryGridItemData getGalleryData() {
202     return selectedData;
203   }
204 
getGalleryGridView()205   public GridView getGalleryGridView() {
206     return galleryGridView;
207   }
208 
209   @Override
onActivityResult(int requestCode, int resultCode, Intent data)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 
setSelected(GalleryGridItemData data, boolean isCopy)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
shouldHide()232   public boolean shouldHide() {
233     return selectedData == null
234         || selectedData.getFilePath() == null
235         || selectedData.getMimeType() == null;
236   }
237 
238   @Override
clearComposer()239   public void clearComposer() {
240     setSelected(null, false);
241   }
242 
243   @Override
onSaveInstanceState(Bundle outState)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
onRequestPermissionsResult( int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)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 
getCursorLoader()271   public CursorLoader getCursorLoader() {
272     return cursorLoader;
273   }
274 
selectedDataIsCopy()275   public boolean selectedDataIsCopy() {
276     return selectedDataIsCopy;
277   }
278 
prepareDataForAttachment(Intent data)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