blob: 2fe2756e91691951c0e869946cd9335861a2ab5d [file] [log] [blame]
Felipe Lemeb012f912016-01-22 16:49:55 -08001/*
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.documentsui;
18
19import static android.os.Environment.isStandardDirectory;
Felipe Leme3e166b22016-02-24 10:17:41 -080020import static android.os.Environment.STANDARD_DIRECTORIES;
Felipe Leme34a9d522016-02-17 10:12:04 -080021import static android.os.storage.StorageVolume.EXTRA_DIRECTORY_NAME;
22import static android.os.storage.StorageVolume.EXTRA_STORAGE_VOLUME;
Felipe Lemeadccb992016-03-09 17:40:49 -080023import static com.android.documentsui.LocalPreferences.getScopedAccessPermissionStatus;
24import static com.android.documentsui.LocalPreferences.PERMISSION_ASK;
25import static com.android.documentsui.LocalPreferences.PERMISSION_ASK_AGAIN;
26import static com.android.documentsui.LocalPreferences.PERMISSION_NEVER_ASK;
27import static com.android.documentsui.LocalPreferences.setScopedAccessPermissionStatus;
28import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED;
Felipe Leme3e166b22016-02-24 10:17:41 -080029import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED;
30import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_DENIED;
Felipe Lemeadccb992016-03-09 17:40:49 -080031import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST;
Felipe Leme3e166b22016-02-24 10:17:41 -080032import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_ERROR;
33import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_GRANTED;
34import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS;
35import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY;
Felipe Lemeadccb992016-03-09 17:40:49 -080036import static com.android.documentsui.Metrics.logInvalidScopedAccessRequest;
37import static com.android.documentsui.Metrics.logValidScopedAccessRequest;
38import static com.android.documentsui.Shared.DEBUG;
Felipe Lemeb012f912016-01-22 16:49:55 -080039
Felipe Lemeadccb992016-03-09 17:40:49 -080040import android.annotation.SuppressLint;
Felipe Lemeb012f912016-01-22 16:49:55 -080041import android.app.Activity;
Felipe Leme560d23a2016-02-17 17:10:45 -080042import android.app.ActivityManager;
Felipe Lemeb012f912016-01-22 16:49:55 -080043import android.app.AlertDialog;
44import android.app.Dialog;
45import android.app.DialogFragment;
46import android.app.FragmentManager;
47import android.app.FragmentTransaction;
Felipe Lemeb012f912016-01-22 16:49:55 -080048import android.content.ContentProviderClient;
Felipe Lemeb012f912016-01-22 16:49:55 -080049import android.content.Context;
50import android.content.DialogInterface;
51import android.content.DialogInterface.OnClickListener;
52import android.content.Intent;
Felipe Leme560d23a2016-02-17 17:10:45 -080053import android.content.UriPermission;
Felipe Lemeb012f912016-01-22 16:49:55 -080054import android.content.pm.PackageManager;
55import android.content.pm.PackageManager.NameNotFoundException;
56import android.net.Uri;
57import android.os.Bundle;
Felipe Leme34a9d522016-02-17 10:12:04 -080058import android.os.Parcelable;
Felipe Lemeb012f912016-01-22 16:49:55 -080059import android.os.RemoteException;
60import android.os.UserHandle;
61import android.os.storage.StorageManager;
Felipe Leme34a9d522016-02-17 10:12:04 -080062import android.os.storage.StorageVolume;
Felipe Lemeb012f912016-01-22 16:49:55 -080063import android.os.storage.VolumeInfo;
64import android.provider.DocumentsContract;
65import android.text.TextUtils;
66import android.util.Log;
Felipe Lemeadccb992016-03-09 17:40:49 -080067import android.view.View;
68import android.widget.CheckBox;
69import android.widget.CompoundButton;
70import android.widget.CompoundButton.OnCheckedChangeListener;
71import android.widget.TextView;
Felipe Lemeb012f912016-01-22 16:49:55 -080072
Ben Kwae3aee182016-02-02 12:11:10 -080073import java.io.File;
74import java.io.IOException;
75import java.util.List;
76
Felipe Lemeb012f912016-01-22 16:49:55 -080077/**
78 * Activity responsible for handling {@link Intent#ACTION_OPEN_EXTERNAL_DOCUMENT}.
79 */
80public class OpenExternalDirectoryActivity extends Activity {
Ben Kwae3aee182016-02-02 12:11:10 -080081 private static final String TAG = "OpenExternalDirectory";
Felipe Lemeb012f912016-01-22 16:49:55 -080082 private static final String FM_TAG = "open_external_directory";
83 private static final String EXTERNAL_STORAGE_AUTH = "com.android.externalstorage.documents";
84 private static final String EXTRA_FILE = "com.android.documentsui.FILE";
85 private static final String EXTRA_APP_LABEL = "com.android.documentsui.APP_LABEL";
86 private static final String EXTRA_VOLUME_LABEL = "com.android.documentsui.VOLUME_LABEL";
Felipe Lemeadccb992016-03-09 17:40:49 -080087 private static final String EXTRA_VOLUME_UUID = "com.android.documentsui.VOLUME_UUID";
Felipe Lemedb892b82016-03-17 18:56:20 -070088 private static final String EXTRA_IS_ROOT = "com.android.documentsui.IS_ROOT";
89 // Special directory name representing the full volume
90 static final String DIRECTORY_ROOT = "ROOT_DIRECTORY";
Felipe Lemeb012f912016-01-22 16:49:55 -080091
Felipe Leme560d23a2016-02-17 17:10:45 -080092 private ContentProviderClient mExternalStorageClient;
93
Felipe Lemeb012f912016-01-22 16:49:55 -080094 @Override
95 public void onCreate(Bundle savedInstanceState) {
96 super.onCreate(savedInstanceState);
Felipe Lemeadccb992016-03-09 17:40:49 -080097 if (savedInstanceState != null) {
98 if (DEBUG) Log.d(TAG, "activity.onCreateDialog(): reusing instance");
99 return;
100 }
Felipe Lemeb012f912016-01-22 16:49:55 -0800101
102 final Intent intent = getIntent();
Felipe Leme34a9d522016-02-17 10:12:04 -0800103 if (intent == null) {
104 if (DEBUG) Log.d(TAG, "missing intent");
Felipe Leme3e166b22016-02-24 10:17:41 -0800105 logInvalidScopedAccessRequest(this, SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS);
Felipe Leme34a9d522016-02-17 10:12:04 -0800106 setResult(RESULT_CANCELED);
107 finish();
108 return;
109 }
110 final Parcelable storageVolume = intent.getParcelableExtra(EXTRA_STORAGE_VOLUME);
111 if (!(storageVolume instanceof StorageVolume)) {
112 if (DEBUG)
113 Log.d(TAG, "extra " + EXTRA_STORAGE_VOLUME + " is not a StorageVolume: "
114 + storageVolume);
Felipe Leme3e166b22016-02-24 10:17:41 -0800115 logInvalidScopedAccessRequest(this, SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS);
Felipe Leme34a9d522016-02-17 10:12:04 -0800116 setResult(RESULT_CANCELED);
117 finish();
118 return;
119 }
Felipe Lemedb892b82016-03-17 18:56:20 -0700120 String directoryName = intent.getStringExtra(EXTRA_DIRECTORY_NAME );
Felipe Leme34a9d522016-02-17 10:12:04 -0800121 if (directoryName == null) {
Felipe Lemedb892b82016-03-17 18:56:20 -0700122 directoryName = DIRECTORY_ROOT;
Felipe Lemeb012f912016-01-22 16:49:55 -0800123 }
Felipe Lemeadccb992016-03-09 17:40:49 -0800124 final StorageVolume volume = (StorageVolume) storageVolume;
125 if (getScopedAccessPermissionStatus(getApplicationContext(), getCallingPackage(),
126 volume.getUuid(), directoryName) == PERMISSION_NEVER_ASK) {
127 logValidScopedAccessRequest(this, directoryName,
128 SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED);
129 setResult(RESULT_CANCELED);
130 finish();
131 return;
132 }
Felipe Lemeb012f912016-01-22 16:49:55 -0800133
Felipe Lemeb012f912016-01-22 16:49:55 -0800134 final int userId = UserHandle.myUserId();
Felipe Lemeadccb992016-03-09 17:40:49 -0800135 if (!showFragment(this, userId, volume, directoryName)) {
Felipe Lemeb012f912016-01-22 16:49:55 -0800136 setResult(RESULT_CANCELED);
137 finish();
138 return;
139 }
140 }
141
Felipe Leme560d23a2016-02-17 17:10:45 -0800142 @Override
143 public void onDestroy() {
144 super.onDestroy();
145 if (mExternalStorageClient != null) {
146 mExternalStorageClient.close();
147 }
148 }
149
Felipe Lemeb012f912016-01-22 16:49:55 -0800150 /**
Felipe Leme34a9d522016-02-17 10:12:04 -0800151 * Validates the given path (volume + directory) and display the appropriate dialog asking the
152 * user to grant access to it.
Felipe Lemeb012f912016-01-22 16:49:55 -0800153 */
Felipe Leme560d23a2016-02-17 17:10:45 -0800154 private static boolean showFragment(OpenExternalDirectoryActivity activity, int userId,
155 StorageVolume storageVolume, String directoryName) {
Felipe Leme34a9d522016-02-17 10:12:04 -0800156 if (DEBUG)
157 Log.d(TAG, "showFragment() for volume " + storageVolume.dump() + ", directory "
158 + directoryName + ", and user " + userId);
Felipe Lemedb892b82016-03-17 18:56:20 -0700159 final boolean isRoot = directoryName.equals(DIRECTORY_ROOT);
160 final File volumeRoot = storageVolume.getPathFile();
Felipe Lemeb012f912016-01-22 16:49:55 -0800161 File file;
162 try {
Felipe Lemedb892b82016-03-17 18:56:20 -0700163 file = isRoot ? volumeRoot : new File(volumeRoot, directoryName).getCanonicalFile();
Felipe Lemeb012f912016-01-22 16:49:55 -0800164 } catch (IOException e) {
Felipe Leme34a9d522016-02-17 10:12:04 -0800165 Log.e(TAG, "Could not get canonical file for volume " + storageVolume.dump()
166 + " and directory " + directoryName);
Felipe Leme3e166b22016-02-24 10:17:41 -0800167 logInvalidScopedAccessRequest(activity, SCOPED_DIRECTORY_ACCESS_ERROR);
Felipe Lemeb012f912016-01-22 16:49:55 -0800168 return false;
169 }
170 final StorageManager sm =
171 (StorageManager) activity.getSystemService(Context.STORAGE_SERVICE);
172
Felipe Lemedb892b82016-03-17 18:56:20 -0700173 final String root, directory;
174 if (isRoot) {
175 root = volumeRoot.getAbsolutePath();
176 directory = ".";
177 } else {
178 root = file.getParent();
179 directory = file.getName();
180 // Verify directory is valid.
181 if (TextUtils.isEmpty(directory) || !isStandardDirectory(directory)) {
182 if (DEBUG)
183 Log.d(TAG, "Directory '" + directory + "' is not standard (full path: '"
184 + file.getAbsolutePath() + "')");
185 logInvalidScopedAccessRequest(activity, SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY);
186 return false;
187 }
Felipe Lemeb012f912016-01-22 16:49:55 -0800188 }
189
Felipe Leme560d23a2016-02-17 17:10:45 -0800190 // Gets volume label and converted path.
Felipe Lemeb012f912016-01-22 16:49:55 -0800191 String volumeLabel = null;
Felipe Lemeadccb992016-03-09 17:40:49 -0800192 String volumeUuid = null;
Felipe Lemeb012f912016-01-22 16:49:55 -0800193 final List<VolumeInfo> volumes = sm.getVolumes();
194 if (DEBUG) Log.d(TAG, "Number of volumes: " + volumes.size());
Felipe Lemedb892b82016-03-17 18:56:20 -0700195 File internalRoot = null;
Felipe Lemeb012f912016-01-22 16:49:55 -0800196 for (VolumeInfo volume : volumes) {
197 if (isRightVolume(volume, root, userId)) {
Felipe Lemedb892b82016-03-17 18:56:20 -0700198 internalRoot = volume.getInternalPathForUser(userId);
Felipe Lemeb012f912016-01-22 16:49:55 -0800199 // Must convert path before calling getDocIdForFileCreateNewDir()
200 if (DEBUG) Log.d(TAG, "Converting " + root + " to " + internalRoot);
Felipe Lemedb892b82016-03-17 18:56:20 -0700201 file = isRoot ? internalRoot : new File(internalRoot, directory);
Felipe Lemeb012f912016-01-22 16:49:55 -0800202 volumeLabel = sm.getBestVolumeDescription(volume);
Felipe Lemeadccb992016-03-09 17:40:49 -0800203 volumeUuid = volume.getFsUuid();
Felipe Lemeb012f912016-01-22 16:49:55 -0800204 break;
205 }
206 }
Felipe Leme560d23a2016-02-17 17:10:45 -0800207
208 // Checks if the user has granted the permission already.
Felipe Lemedb892b82016-03-17 18:56:20 -0700209 final Intent intent = getIntentForExistingPermission(activity, isRoot, internalRoot, file);
Felipe Leme560d23a2016-02-17 17:10:45 -0800210 if (intent != null) {
Felipe Leme3e166b22016-02-24 10:17:41 -0800211 logValidScopedAccessRequest(activity, directory,
212 SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED);
Felipe Leme560d23a2016-02-17 17:10:45 -0800213 activity.setResult(RESULT_OK, intent);
214 activity.finish();
215 return true;
216 }
217
Felipe Lemeb012f912016-01-22 16:49:55 -0800218 if (volumeLabel == null) {
Felipe Leme34a9d522016-02-17 10:12:04 -0800219 Log.e(TAG, "Could not get volume for " + file);
Felipe Leme3e166b22016-02-24 10:17:41 -0800220 logInvalidScopedAccessRequest(activity, SCOPED_DIRECTORY_ACCESS_ERROR);
Felipe Lemeb012f912016-01-22 16:49:55 -0800221 return false;
222 }
223
224 // Gets the package label.
225 final String appLabel = getAppLabel(activity);
226 if (appLabel == null) {
Felipe Leme3e166b22016-02-24 10:17:41 -0800227 // Error already logged.
Felipe Lemeb012f912016-01-22 16:49:55 -0800228 return false;
229 }
230
231 // Sets args that will be retrieve on onCreate()
232 final Bundle args = new Bundle();
233 args.putString(EXTRA_FILE, file.getAbsolutePath());
234 args.putString(EXTRA_VOLUME_LABEL, volumeLabel);
Felipe Lemeadccb992016-03-09 17:40:49 -0800235 args.putString(EXTRA_VOLUME_UUID, volumeUuid);
Felipe Lemeb012f912016-01-22 16:49:55 -0800236 args.putString(EXTRA_APP_LABEL, appLabel);
Felipe Lemedb892b82016-03-17 18:56:20 -0700237 args.putBoolean(EXTRA_IS_ROOT, isRoot);
Felipe Lemeb012f912016-01-22 16:49:55 -0800238
239 final FragmentManager fm = activity.getFragmentManager();
240 final FragmentTransaction ft = fm.beginTransaction();
241 final OpenExternalDirectoryDialogFragment fragment =
242 new OpenExternalDirectoryDialogFragment();
243 fragment.setArguments(args);
244 ft.add(fragment, FM_TAG);
245 ft.commitAllowingStateLoss();
246
247 return true;
248 }
249
250 private static String getAppLabel(Activity activity) {
251 final String packageName = activity.getCallingPackage();
252 final PackageManager pm = activity.getPackageManager();
253 try {
254 return pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)).toString();
255 } catch (NameNotFoundException e) {
Felipe Leme3e166b22016-02-24 10:17:41 -0800256 logInvalidScopedAccessRequest(activity, SCOPED_DIRECTORY_ACCESS_ERROR);
Felipe Lemeb012f912016-01-22 16:49:55 -0800257 Log.w(TAG, "Could not get label for package " + packageName);
258 return null;
259 }
260 }
261
262 private static boolean isRightVolume(VolumeInfo volume, String root, int userId) {
263 final File userPath = volume.getPathForUser(userId);
264 final String path = userPath == null ? null : volume.getPathForUser(userId).getPath();
265 final boolean isVisible = volume.isVisibleForWrite(userId);
Felipe Leme34a9d522016-02-17 10:12:04 -0800266 if (DEBUG)
Felipe Lemeb012f912016-01-22 16:49:55 -0800267 Log.d(TAG, "Volume: " + volume + " userId: " + userId + " root: " + root
268 + " volumePath: " + volume.getPath().getPath()
269 + " pathForUser: " + path
270 + " internalPathForUser: " + volume.getInternalPath()
271 + " isVisible: " + isVisible);
Felipe Leme34a9d522016-02-17 10:12:04 -0800272
Felipe Lemeb012f912016-01-22 16:49:55 -0800273 return volume.isVisibleForWrite(userId) && root.equals(path);
274 }
275
Felipe Leme3e166b22016-02-24 10:17:41 -0800276 private static Uri getGrantedUriPermission(Context context, ContentProviderClient provider,
277 File file) {
Felipe Lemeb012f912016-01-22 16:49:55 -0800278 // Calls ExternalStorageProvider to get the doc id for the file
279 final Bundle bundle;
280 try {
281 bundle = provider.call("getDocIdForFileCreateNewDir", file.getPath(), null);
282 } catch (RemoteException e) {
283 Log.e(TAG, "Did not get doc id from External Storage provider for " + file, e);
Felipe Leme3e166b22016-02-24 10:17:41 -0800284 logInvalidScopedAccessRequest(context, SCOPED_DIRECTORY_ACCESS_ERROR);
Felipe Lemeb012f912016-01-22 16:49:55 -0800285 return null;
286 }
287 final String docId = bundle == null ? null : bundle.getString("DOC_ID");
288 if (docId == null) {
289 Log.e(TAG, "Did not get doc id from External Storage provider for " + file);
Felipe Leme3e166b22016-02-24 10:17:41 -0800290 logInvalidScopedAccessRequest(context, SCOPED_DIRECTORY_ACCESS_ERROR);
Felipe Lemeb012f912016-01-22 16:49:55 -0800291 return null;
292 }
Ben Kwaffa829f2016-03-22 11:11:46 -0700293 if (DEBUG) Log.d(TAG, "doc id for " + file + ": " + docId);
Felipe Lemeb012f912016-01-22 16:49:55 -0800294
295 final Uri uri = DocumentsContract.buildTreeDocumentUri(EXTERNAL_STORAGE_AUTH, docId);
296 if (uri == null) {
297 Log.e(TAG, "Could not get URI for doc id " + docId);
298 return null;
299 }
Felipe Lemeb012f912016-01-22 16:49:55 -0800300 if (DEBUG) Log.d(TAG, "URI for " + file + ": " + uri);
Felipe Leme560d23a2016-02-17 17:10:45 -0800301 return uri;
302 }
303
Felipe Leme3e166b22016-02-24 10:17:41 -0800304 private static Intent createGrantedUriPermissionsIntent(Context context,
305 ContentProviderClient provider, File file) {
306 final Uri uri = getGrantedUriPermission(context, provider, file);
Felipe Leme560d23a2016-02-17 17:10:45 -0800307 return createGrantedUriPermissionsIntent(uri);
308 }
309
310 private static Intent createGrantedUriPermissionsIntent(Uri uri) {
Felipe Lemeb012f912016-01-22 16:49:55 -0800311 final Intent intent = new Intent();
312 intent.setData(uri);
313 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
314 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
315 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
316 | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
317 return intent;
318 }
319
Felipe Leme560d23a2016-02-17 17:10:45 -0800320 private static Intent getIntentForExistingPermission(OpenExternalDirectoryActivity activity,
Felipe Lemedb892b82016-03-17 18:56:20 -0700321 boolean isRoot, File root, File file) {
Felipe Leme560d23a2016-02-17 17:10:45 -0800322 final String packageName = activity.getCallingPackage();
Felipe Lemedb892b82016-03-17 18:56:20 -0700323 final ContentProviderClient storageClient = activity.getExternalStorageClient();
324 final Uri grantedUri = getGrantedUriPermission(activity, storageClient, file);
325 final Uri rootUri = root.equals(file) ? grantedUri
326 : getGrantedUriPermission(activity, storageClient, root);
327
Felipe Leme560d23a2016-02-17 17:10:45 -0800328 if (DEBUG)
Felipe Lemedb892b82016-03-17 18:56:20 -0700329 Log.d(TAG, "checking if " + packageName + " already has permission for " + grantedUri
330 + " or its root (" + rootUri + ")");
Felipe Leme560d23a2016-02-17 17:10:45 -0800331 final ActivityManager am =
332 (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
333 for (UriPermission uriPermission : am.getGrantedUriPermissions(packageName).getList()) {
334 final Uri uri = uriPermission.getUri();
Felipe Lemedb892b82016-03-17 18:56:20 -0700335 if (uri == null) {
336 Log.w(TAG, "null URI for " + uriPermission);
337 continue;
338 }
339 if (uri.equals(grantedUri) || uri.equals(rootUri)) {
Felipe Leme560d23a2016-02-17 17:10:45 -0800340 if (DEBUG) Log.d(TAG, packageName + " already has permission: " + uriPermission);
Felipe Lemedb892b82016-03-17 18:56:20 -0700341 return createGrantedUriPermissionsIntent(grantedUri);
Felipe Leme560d23a2016-02-17 17:10:45 -0800342 }
343 }
344 if (DEBUG) Log.d(TAG, packageName + " does not have permission for " + grantedUri);
345 return null;
346 }
347
Ben Kwae3aee182016-02-02 12:11:10 -0800348 public static class OpenExternalDirectoryDialogFragment extends DialogFragment {
Felipe Lemeb012f912016-01-22 16:49:55 -0800349
350 private File mFile;
Felipe Lemeadccb992016-03-09 17:40:49 -0800351 private String mVolumeUuid;
Felipe Lemeb012f912016-01-22 16:49:55 -0800352 private String mVolumeLabel;
353 private String mAppLabel;
Felipe Lemedb892b82016-03-17 18:56:20 -0700354 private boolean mIsRoot;
Felipe Lemeadccb992016-03-09 17:40:49 -0800355 private CheckBox mDontAskAgain;
Felipe Leme560d23a2016-02-17 17:10:45 -0800356 private OpenExternalDirectoryActivity mActivity;
Felipe Lemeadccb992016-03-09 17:40:49 -0800357 private AlertDialog mDialog;
Felipe Lemeb012f912016-01-22 16:49:55 -0800358
359 @Override
360 public void onCreate(Bundle savedInstanceState) {
361 super.onCreate(savedInstanceState);
Felipe Lemeadccb992016-03-09 17:40:49 -0800362 setRetainInstance(true);
Felipe Lemeb012f912016-01-22 16:49:55 -0800363 final Bundle args = getArguments();
364 if (args != null) {
365 mFile = new File(args.getString(EXTRA_FILE));
Felipe Lemeadccb992016-03-09 17:40:49 -0800366 mVolumeUuid = args.getString(EXTRA_VOLUME_UUID);
Felipe Lemeb012f912016-01-22 16:49:55 -0800367 mVolumeLabel = args.getString(EXTRA_VOLUME_LABEL);
368 mAppLabel = args.getString(EXTRA_APP_LABEL);
Felipe Lemedb892b82016-03-17 18:56:20 -0700369 mIsRoot = args.getBoolean(EXTRA_IS_ROOT);
Felipe Lemeb012f912016-01-22 16:49:55 -0800370 }
Felipe Leme560d23a2016-02-17 17:10:45 -0800371 mActivity = (OpenExternalDirectoryActivity) getActivity();
Felipe Lemeb012f912016-01-22 16:49:55 -0800372 }
373
374 @Override
Felipe Lemeadccb992016-03-09 17:40:49 -0800375 public void onDestroyView() {
376 // Workaround for https://code.google.com/p/android/issues/detail?id=17423
377 if (mDialog != null && getRetainInstance()) {
378 mDialog.setDismissMessage(null);
379 }
380 super.onDestroyView();
381 }
382
383 @Override
Felipe Lemeb012f912016-01-22 16:49:55 -0800384 public Dialog onCreateDialog(Bundle savedInstanceState) {
Felipe Lemeadccb992016-03-09 17:40:49 -0800385 if (mDialog != null) {
386 if (DEBUG) Log.d(TAG, "fragment.onCreateDialog(): reusing dialog");
387 return mDialog;
388 }
389 if (mActivity != getActivity()) {
390 // Sanity check.
391 Log.wtf(TAG, "activity references don't match on onCreateDialog(): mActivity = "
392 + mActivity + " , getActivity() = " + getActivity());
393 mActivity = (OpenExternalDirectoryActivity) getActivity();
394 }
Felipe Leme3e166b22016-02-24 10:17:41 -0800395 final String directory = mFile.getName();
Felipe Lemedb892b82016-03-17 18:56:20 -0700396 final String directoryName = mIsRoot ? DIRECTORY_ROOT : directory;
Felipe Lemeadccb992016-03-09 17:40:49 -0800397 final Context context = mActivity.getApplicationContext();
Felipe Lemeb012f912016-01-22 16:49:55 -0800398 final OnClickListener listener = new OnClickListener() {
399
400 @Override
401 public void onClick(DialogInterface dialog, int which) {
402 Intent intent = null;
403 if (which == DialogInterface.BUTTON_POSITIVE) {
Felipe Leme3e166b22016-02-24 10:17:41 -0800404 intent = createGrantedUriPermissionsIntent(mActivity,
Felipe Leme560d23a2016-02-17 17:10:45 -0800405 mActivity.getExternalStorageClient(), mFile);
Felipe Lemeb012f912016-01-22 16:49:55 -0800406 }
407 if (which == DialogInterface.BUTTON_NEGATIVE || intent == null) {
Felipe Lemedb892b82016-03-17 18:56:20 -0700408 logValidScopedAccessRequest(mActivity, directoryName,
Felipe Leme3e166b22016-02-24 10:17:41 -0800409 SCOPED_DIRECTORY_ACCESS_DENIED);
Felipe Lemeadccb992016-03-09 17:40:49 -0800410 final boolean checked = mDontAskAgain.isChecked();
411 if (checked) {
412 logValidScopedAccessRequest(mActivity, directory,
413 SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST);
414 setScopedAccessPermissionStatus(context, mActivity.getCallingPackage(),
Felipe Lemedb892b82016-03-17 18:56:20 -0700415 mVolumeUuid, directoryName, PERMISSION_NEVER_ASK);
Felipe Lemeadccb992016-03-09 17:40:49 -0800416 } else {
417 setScopedAccessPermissionStatus(context, mActivity.getCallingPackage(),
Felipe Lemedb892b82016-03-17 18:56:20 -0700418 mVolumeUuid, directoryName, PERMISSION_ASK_AGAIN);
Felipe Lemeadccb992016-03-09 17:40:49 -0800419 }
420 mActivity.setResult(RESULT_CANCELED);
Felipe Lemeb012f912016-01-22 16:49:55 -0800421 } else {
Felipe Lemeadccb992016-03-09 17:40:49 -0800422 logValidScopedAccessRequest(mActivity, directory,
Felipe Leme3e166b22016-02-24 10:17:41 -0800423 SCOPED_DIRECTORY_ACCESS_GRANTED);
Felipe Lemeadccb992016-03-09 17:40:49 -0800424 mActivity.setResult(RESULT_OK, intent);
Felipe Lemeb012f912016-01-22 16:49:55 -0800425 }
Felipe Lemeadccb992016-03-09 17:40:49 -0800426 mActivity.finish();
Felipe Lemeb012f912016-01-22 16:49:55 -0800427 }
428 };
429
Felipe Lemeadccb992016-03-09 17:40:49 -0800430 @SuppressLint("InflateParams")
431 // It's ok pass null ViewRoot on AlertDialogs.
432 final View view = View.inflate(mActivity, R.layout.dialog_open_scoped_directory, null);
Felipe Lemedb892b82016-03-17 18:56:20 -0700433 final CharSequence message;
434 if (mIsRoot) {
435 message = TextUtils.expandTemplate(getText(
436 R.string.open_external_dialog_root_request), mAppLabel, mVolumeLabel);
437 } else {
438 message = TextUtils.expandTemplate(getText(R.string.open_external_dialog_request),
439 mAppLabel, directory, mVolumeLabel);
440 }
Felipe Lemeadccb992016-03-09 17:40:49 -0800441 final TextView messageField = (TextView) view.findViewById(R.id.message);
442 messageField.setText(message);
Felipe Leme493611f2016-03-15 16:29:17 -0700443 mDialog = new AlertDialog.Builder(mActivity, R.style.Theme_AppCompat_Light_Dialog_Alert)
Felipe Lemeadccb992016-03-09 17:40:49 -0800444 .setView(view)
Felipe Lemeb012f912016-01-22 16:49:55 -0800445 .setPositiveButton(R.string.allow, listener)
446 .setNegativeButton(R.string.deny, listener)
447 .create();
Felipe Lemeadccb992016-03-09 17:40:49 -0800448
449 mDontAskAgain = (CheckBox) view.findViewById(R.id.do_not_ask_checkbox);
450 if (getScopedAccessPermissionStatus(context, mActivity.getCallingPackage(),
Felipe Lemedb892b82016-03-17 18:56:20 -0700451 mVolumeUuid, directoryName) == PERMISSION_ASK_AGAIN) {
Felipe Lemeadccb992016-03-09 17:40:49 -0800452 mDontAskAgain.setVisibility(View.VISIBLE);
453 mDontAskAgain.setOnCheckedChangeListener(new OnCheckedChangeListener() {
454
455 @Override
456 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
457 mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(!isChecked);
458 }
459 });
460 }
461
462 return mDialog;
Felipe Lemeb012f912016-01-22 16:49:55 -0800463 }
464
465 @Override
466 public void onCancel(DialogInterface dialog) {
467 super.onCancel(dialog);
468 final Activity activity = getActivity();
Felipe Leme3e166b22016-02-24 10:17:41 -0800469 logValidScopedAccessRequest(activity, mFile.getName(), SCOPED_DIRECTORY_ACCESS_DENIED);
Felipe Lemeb012f912016-01-22 16:49:55 -0800470 activity.setResult(RESULT_CANCELED);
471 activity.finish();
472 }
Felipe Leme560d23a2016-02-17 17:10:45 -0800473 }
Felipe Lemeb012f912016-01-22 16:49:55 -0800474
Felipe Leme560d23a2016-02-17 17:10:45 -0800475 private synchronized ContentProviderClient getExternalStorageClient() {
476 if (mExternalStorageClient == null) {
477 mExternalStorageClient =
478 getContentResolver().acquireContentProviderClient(EXTERNAL_STORAGE_AUTH);
Felipe Lemeb012f912016-01-22 16:49:55 -0800479 }
Felipe Leme560d23a2016-02-17 17:10:45 -0800480 return mExternalStorageClient;
Felipe Lemeb012f912016-01-22 16:49:55 -0800481 }
482}