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