blob: 854be0bee826f303c03dc7fad2e11a2d78bdde2f [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";
Felipe Leme2ac87692016-03-29 19:06:02 -070089 private static final String EXTRA_IS_PRIMARY = "com.android.documentsui.IS_PRIMARY";
Felipe Lemedb892b82016-03-17 18:56:20 -070090 // Special directory name representing the full volume
91 static final String DIRECTORY_ROOT = "ROOT_DIRECTORY";
Felipe Lemeb012f912016-01-22 16:49:55 -080092
Felipe Leme560d23a2016-02-17 17:10:45 -080093 private ContentProviderClient mExternalStorageClient;
94
Felipe Lemeb012f912016-01-22 16:49:55 -080095 @Override
96 public void onCreate(Bundle savedInstanceState) {
97 super.onCreate(savedInstanceState);
Felipe Lemeadccb992016-03-09 17:40:49 -080098 if (savedInstanceState != null) {
99 if (DEBUG) Log.d(TAG, "activity.onCreateDialog(): reusing instance");
100 return;
101 }
Felipe Lemeb012f912016-01-22 16:49:55 -0800102
103 final Intent intent = getIntent();
Felipe Leme34a9d522016-02-17 10:12:04 -0800104 if (intent == null) {
105 if (DEBUG) Log.d(TAG, "missing intent");
Felipe Leme3e166b22016-02-24 10:17:41 -0800106 logInvalidScopedAccessRequest(this, SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS);
Felipe Leme34a9d522016-02-17 10:12:04 -0800107 setResult(RESULT_CANCELED);
108 finish();
109 return;
110 }
111 final Parcelable storageVolume = intent.getParcelableExtra(EXTRA_STORAGE_VOLUME);
112 if (!(storageVolume instanceof StorageVolume)) {
113 if (DEBUG)
114 Log.d(TAG, "extra " + EXTRA_STORAGE_VOLUME + " is not a StorageVolume: "
115 + storageVolume);
Felipe Leme3e166b22016-02-24 10:17:41 -0800116 logInvalidScopedAccessRequest(this, SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS);
Felipe Leme34a9d522016-02-17 10:12:04 -0800117 setResult(RESULT_CANCELED);
118 finish();
119 return;
120 }
Felipe Lemedb892b82016-03-17 18:56:20 -0700121 String directoryName = intent.getStringExtra(EXTRA_DIRECTORY_NAME );
Felipe Leme34a9d522016-02-17 10:12:04 -0800122 if (directoryName == null) {
Felipe Lemedb892b82016-03-17 18:56:20 -0700123 directoryName = DIRECTORY_ROOT;
Felipe Lemeb012f912016-01-22 16:49:55 -0800124 }
Felipe Lemeadccb992016-03-09 17:40:49 -0800125 final StorageVolume volume = (StorageVolume) storageVolume;
126 if (getScopedAccessPermissionStatus(getApplicationContext(), getCallingPackage(),
127 volume.getUuid(), directoryName) == PERMISSION_NEVER_ASK) {
128 logValidScopedAccessRequest(this, directoryName,
129 SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED);
130 setResult(RESULT_CANCELED);
131 finish();
132 return;
133 }
Felipe Lemeb012f912016-01-22 16:49:55 -0800134
Felipe Lemeb012f912016-01-22 16:49:55 -0800135 final int userId = UserHandle.myUserId();
Felipe Lemeadccb992016-03-09 17:40:49 -0800136 if (!showFragment(this, userId, volume, directoryName)) {
Felipe Lemeb012f912016-01-22 16:49:55 -0800137 setResult(RESULT_CANCELED);
138 finish();
139 return;
140 }
141 }
142
Felipe Leme560d23a2016-02-17 17:10:45 -0800143 @Override
144 public void onDestroy() {
145 super.onDestroy();
146 if (mExternalStorageClient != null) {
147 mExternalStorageClient.close();
148 }
149 }
150
Felipe Lemeb012f912016-01-22 16:49:55 -0800151 /**
Felipe Leme34a9d522016-02-17 10:12:04 -0800152 * Validates the given path (volume + directory) and display the appropriate dialog asking the
153 * user to grant access to it.
Felipe Lemeb012f912016-01-22 16:49:55 -0800154 */
Felipe Leme560d23a2016-02-17 17:10:45 -0800155 private static boolean showFragment(OpenExternalDirectoryActivity activity, int userId,
156 StorageVolume storageVolume, String directoryName) {
Felipe Leme34a9d522016-02-17 10:12:04 -0800157 if (DEBUG)
158 Log.d(TAG, "showFragment() for volume " + storageVolume.dump() + ", directory "
159 + directoryName + ", and user " + userId);
Felipe Lemedb892b82016-03-17 18:56:20 -0700160 final boolean isRoot = directoryName.equals(DIRECTORY_ROOT);
Felipe Leme2ac87692016-03-29 19:06:02 -0700161 final boolean isPrimary = storageVolume.isPrimary();
162
163 if (isRoot && isPrimary) {
164 if (DEBUG) Log.d(TAG, "root access requested on primary volume");
165 return false;
166 }
167
Felipe Lemedb892b82016-03-17 18:56:20 -0700168 final File volumeRoot = storageVolume.getPathFile();
Felipe Lemeb012f912016-01-22 16:49:55 -0800169 File file;
170 try {
Felipe Lemedb892b82016-03-17 18:56:20 -0700171 file = isRoot ? volumeRoot : new File(volumeRoot, directoryName).getCanonicalFile();
Felipe Lemeb012f912016-01-22 16:49:55 -0800172 } catch (IOException e) {
Felipe Leme34a9d522016-02-17 10:12:04 -0800173 Log.e(TAG, "Could not get canonical file for volume " + storageVolume.dump()
174 + " and directory " + directoryName);
Felipe Leme3e166b22016-02-24 10:17:41 -0800175 logInvalidScopedAccessRequest(activity, SCOPED_DIRECTORY_ACCESS_ERROR);
Felipe Lemeb012f912016-01-22 16:49:55 -0800176 return false;
177 }
178 final StorageManager sm =
179 (StorageManager) activity.getSystemService(Context.STORAGE_SERVICE);
180
Felipe Lemedb892b82016-03-17 18:56:20 -0700181 final String root, directory;
182 if (isRoot) {
183 root = volumeRoot.getAbsolutePath();
184 directory = ".";
185 } else {
186 root = file.getParent();
187 directory = file.getName();
188 // Verify directory is valid.
189 if (TextUtils.isEmpty(directory) || !isStandardDirectory(directory)) {
190 if (DEBUG)
191 Log.d(TAG, "Directory '" + directory + "' is not standard (full path: '"
192 + file.getAbsolutePath() + "')");
193 logInvalidScopedAccessRequest(activity, SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY);
194 return false;
195 }
Felipe Lemeb012f912016-01-22 16:49:55 -0800196 }
197
Felipe Leme560d23a2016-02-17 17:10:45 -0800198 // Gets volume label and converted path.
Felipe Lemeb012f912016-01-22 16:49:55 -0800199 String volumeLabel = null;
Felipe Lemeadccb992016-03-09 17:40:49 -0800200 String volumeUuid = null;
Felipe Lemeb012f912016-01-22 16:49:55 -0800201 final List<VolumeInfo> volumes = sm.getVolumes();
202 if (DEBUG) Log.d(TAG, "Number of volumes: " + volumes.size());
Felipe Lemedb892b82016-03-17 18:56:20 -0700203 File internalRoot = null;
Felipe Lemeb012f912016-01-22 16:49:55 -0800204 for (VolumeInfo volume : volumes) {
205 if (isRightVolume(volume, root, userId)) {
Felipe Lemedb892b82016-03-17 18:56:20 -0700206 internalRoot = volume.getInternalPathForUser(userId);
Felipe Lemeb012f912016-01-22 16:49:55 -0800207 // Must convert path before calling getDocIdForFileCreateNewDir()
208 if (DEBUG) Log.d(TAG, "Converting " + root + " to " + internalRoot);
Felipe Lemedb892b82016-03-17 18:56:20 -0700209 file = isRoot ? internalRoot : new File(internalRoot, directory);
Felipe Lemeb012f912016-01-22 16:49:55 -0800210 volumeLabel = sm.getBestVolumeDescription(volume);
Felipe Lemeadccb992016-03-09 17:40:49 -0800211 volumeUuid = volume.getFsUuid();
Felipe Lemeb012f912016-01-22 16:49:55 -0800212 break;
213 }
214 }
Felipe Leme560d23a2016-02-17 17:10:45 -0800215
216 // Checks if the user has granted the permission already.
Felipe Lemedb892b82016-03-17 18:56:20 -0700217 final Intent intent = getIntentForExistingPermission(activity, isRoot, internalRoot, file);
Felipe Leme560d23a2016-02-17 17:10:45 -0800218 if (intent != null) {
Felipe Leme3e166b22016-02-24 10:17:41 -0800219 logValidScopedAccessRequest(activity, directory,
220 SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED);
Felipe Leme560d23a2016-02-17 17:10:45 -0800221 activity.setResult(RESULT_OK, intent);
222 activity.finish();
223 return true;
224 }
225
Felipe Lemeb012f912016-01-22 16:49:55 -0800226 if (volumeLabel == null) {
Felipe Leme34a9d522016-02-17 10:12:04 -0800227 Log.e(TAG, "Could not get volume for " + file);
Felipe Leme3e166b22016-02-24 10:17:41 -0800228 logInvalidScopedAccessRequest(activity, SCOPED_DIRECTORY_ACCESS_ERROR);
Felipe Lemeb012f912016-01-22 16:49:55 -0800229 return false;
230 }
231
232 // Gets the package label.
233 final String appLabel = getAppLabel(activity);
234 if (appLabel == null) {
Felipe Leme3e166b22016-02-24 10:17:41 -0800235 // Error already logged.
Felipe Lemeb012f912016-01-22 16:49:55 -0800236 return false;
237 }
238
239 // Sets args that will be retrieve on onCreate()
240 final Bundle args = new Bundle();
241 args.putString(EXTRA_FILE, file.getAbsolutePath());
242 args.putString(EXTRA_VOLUME_LABEL, volumeLabel);
Felipe Lemeadccb992016-03-09 17:40:49 -0800243 args.putString(EXTRA_VOLUME_UUID, volumeUuid);
Felipe Lemeb012f912016-01-22 16:49:55 -0800244 args.putString(EXTRA_APP_LABEL, appLabel);
Felipe Lemedb892b82016-03-17 18:56:20 -0700245 args.putBoolean(EXTRA_IS_ROOT, isRoot);
Felipe Leme2ac87692016-03-29 19:06:02 -0700246 args.putBoolean(EXTRA_IS_PRIMARY, isPrimary);
Felipe Lemeb012f912016-01-22 16:49:55 -0800247
248 final FragmentManager fm = activity.getFragmentManager();
249 final FragmentTransaction ft = fm.beginTransaction();
250 final OpenExternalDirectoryDialogFragment fragment =
251 new OpenExternalDirectoryDialogFragment();
252 fragment.setArguments(args);
253 ft.add(fragment, FM_TAG);
254 ft.commitAllowingStateLoss();
255
256 return true;
257 }
258
259 private static String getAppLabel(Activity activity) {
260 final String packageName = activity.getCallingPackage();
261 final PackageManager pm = activity.getPackageManager();
262 try {
263 return pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)).toString();
264 } catch (NameNotFoundException e) {
Felipe Leme3e166b22016-02-24 10:17:41 -0800265 logInvalidScopedAccessRequest(activity, SCOPED_DIRECTORY_ACCESS_ERROR);
Felipe Lemeb012f912016-01-22 16:49:55 -0800266 Log.w(TAG, "Could not get label for package " + packageName);
267 return null;
268 }
269 }
270
271 private static boolean isRightVolume(VolumeInfo volume, String root, int userId) {
272 final File userPath = volume.getPathForUser(userId);
273 final String path = userPath == null ? null : volume.getPathForUser(userId).getPath();
274 final boolean isVisible = volume.isVisibleForWrite(userId);
Felipe Leme34a9d522016-02-17 10:12:04 -0800275 if (DEBUG)
Felipe Lemeb012f912016-01-22 16:49:55 -0800276 Log.d(TAG, "Volume: " + volume + " userId: " + userId + " root: " + root
277 + " volumePath: " + volume.getPath().getPath()
278 + " pathForUser: " + path
279 + " internalPathForUser: " + volume.getInternalPath()
280 + " isVisible: " + isVisible);
Felipe Leme34a9d522016-02-17 10:12:04 -0800281
Felipe Lemeb012f912016-01-22 16:49:55 -0800282 return volume.isVisibleForWrite(userId) && root.equals(path);
283 }
284
Felipe Leme3e166b22016-02-24 10:17:41 -0800285 private static Uri getGrantedUriPermission(Context context, ContentProviderClient provider,
286 File file) {
Felipe Lemeb012f912016-01-22 16:49:55 -0800287 // Calls ExternalStorageProvider to get the doc id for the file
288 final Bundle bundle;
289 try {
290 bundle = provider.call("getDocIdForFileCreateNewDir", file.getPath(), null);
291 } catch (RemoteException e) {
292 Log.e(TAG, "Did not get doc id from External Storage provider for " + file, e);
Felipe Leme3e166b22016-02-24 10:17:41 -0800293 logInvalidScopedAccessRequest(context, SCOPED_DIRECTORY_ACCESS_ERROR);
Felipe Lemeb012f912016-01-22 16:49:55 -0800294 return null;
295 }
296 final String docId = bundle == null ? null : bundle.getString("DOC_ID");
297 if (docId == null) {
298 Log.e(TAG, "Did not get doc id from External Storage provider for " + file);
Felipe Leme3e166b22016-02-24 10:17:41 -0800299 logInvalidScopedAccessRequest(context, SCOPED_DIRECTORY_ACCESS_ERROR);
Felipe Lemeb012f912016-01-22 16:49:55 -0800300 return null;
301 }
Ben Kwaffa829f2016-03-22 11:11:46 -0700302 if (DEBUG) Log.d(TAG, "doc id for " + file + ": " + docId);
Felipe Lemeb012f912016-01-22 16:49:55 -0800303
304 final Uri uri = DocumentsContract.buildTreeDocumentUri(EXTERNAL_STORAGE_AUTH, docId);
305 if (uri == null) {
306 Log.e(TAG, "Could not get URI for doc id " + docId);
307 return null;
308 }
Felipe Lemeb012f912016-01-22 16:49:55 -0800309 if (DEBUG) Log.d(TAG, "URI for " + file + ": " + uri);
Felipe Leme560d23a2016-02-17 17:10:45 -0800310 return uri;
311 }
312
Felipe Leme3e166b22016-02-24 10:17:41 -0800313 private static Intent createGrantedUriPermissionsIntent(Context context,
314 ContentProviderClient provider, File file) {
315 final Uri uri = getGrantedUriPermission(context, provider, file);
Felipe Leme560d23a2016-02-17 17:10:45 -0800316 return createGrantedUriPermissionsIntent(uri);
317 }
318
319 private static Intent createGrantedUriPermissionsIntent(Uri uri) {
Felipe Lemeb012f912016-01-22 16:49:55 -0800320 final Intent intent = new Intent();
321 intent.setData(uri);
322 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
323 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
324 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
325 | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
326 return intent;
327 }
328
Felipe Leme560d23a2016-02-17 17:10:45 -0800329 private static Intent getIntentForExistingPermission(OpenExternalDirectoryActivity activity,
Felipe Lemedb892b82016-03-17 18:56:20 -0700330 boolean isRoot, File root, File file) {
Felipe Leme560d23a2016-02-17 17:10:45 -0800331 final String packageName = activity.getCallingPackage();
Felipe Lemedb892b82016-03-17 18:56:20 -0700332 final ContentProviderClient storageClient = activity.getExternalStorageClient();
333 final Uri grantedUri = getGrantedUriPermission(activity, storageClient, file);
334 final Uri rootUri = root.equals(file) ? grantedUri
335 : getGrantedUriPermission(activity, storageClient, root);
336
Felipe Leme560d23a2016-02-17 17:10:45 -0800337 if (DEBUG)
Felipe Lemedb892b82016-03-17 18:56:20 -0700338 Log.d(TAG, "checking if " + packageName + " already has permission for " + grantedUri
339 + " or its root (" + rootUri + ")");
Felipe Leme560d23a2016-02-17 17:10:45 -0800340 final ActivityManager am =
341 (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
342 for (UriPermission uriPermission : am.getGrantedUriPermissions(packageName).getList()) {
343 final Uri uri = uriPermission.getUri();
Felipe Lemedb892b82016-03-17 18:56:20 -0700344 if (uri == null) {
345 Log.w(TAG, "null URI for " + uriPermission);
346 continue;
347 }
348 if (uri.equals(grantedUri) || uri.equals(rootUri)) {
Felipe Leme560d23a2016-02-17 17:10:45 -0800349 if (DEBUG) Log.d(TAG, packageName + " already has permission: " + uriPermission);
Felipe Lemedb892b82016-03-17 18:56:20 -0700350 return createGrantedUriPermissionsIntent(grantedUri);
Felipe Leme560d23a2016-02-17 17:10:45 -0800351 }
352 }
353 if (DEBUG) Log.d(TAG, packageName + " does not have permission for " + grantedUri);
354 return null;
355 }
356
Ben Kwae3aee182016-02-02 12:11:10 -0800357 public static class OpenExternalDirectoryDialogFragment extends DialogFragment {
Felipe Lemeb012f912016-01-22 16:49:55 -0800358
359 private File mFile;
Felipe Lemeadccb992016-03-09 17:40:49 -0800360 private String mVolumeUuid;
Felipe Lemeb012f912016-01-22 16:49:55 -0800361 private String mVolumeLabel;
362 private String mAppLabel;
Felipe Lemedb892b82016-03-17 18:56:20 -0700363 private boolean mIsRoot;
Felipe Leme2ac87692016-03-29 19:06:02 -0700364 private boolean mIsPrimary;
Felipe Lemeadccb992016-03-09 17:40:49 -0800365 private CheckBox mDontAskAgain;
Felipe Leme560d23a2016-02-17 17:10:45 -0800366 private OpenExternalDirectoryActivity mActivity;
Felipe Lemeadccb992016-03-09 17:40:49 -0800367 private AlertDialog mDialog;
Felipe Lemeb012f912016-01-22 16:49:55 -0800368
369 @Override
370 public void onCreate(Bundle savedInstanceState) {
371 super.onCreate(savedInstanceState);
Felipe Lemeadccb992016-03-09 17:40:49 -0800372 setRetainInstance(true);
Felipe Lemeb012f912016-01-22 16:49:55 -0800373 final Bundle args = getArguments();
374 if (args != null) {
375 mFile = new File(args.getString(EXTRA_FILE));
Felipe Lemeadccb992016-03-09 17:40:49 -0800376 mVolumeUuid = args.getString(EXTRA_VOLUME_UUID);
Felipe Lemeb012f912016-01-22 16:49:55 -0800377 mVolumeLabel = args.getString(EXTRA_VOLUME_LABEL);
378 mAppLabel = args.getString(EXTRA_APP_LABEL);
Felipe Lemedb892b82016-03-17 18:56:20 -0700379 mIsRoot = args.getBoolean(EXTRA_IS_ROOT);
Felipe Leme2ac87692016-03-29 19:06:02 -0700380 mIsPrimary= args.getBoolean(EXTRA_IS_PRIMARY);
Felipe Lemeb012f912016-01-22 16:49:55 -0800381 }
Felipe Leme560d23a2016-02-17 17:10:45 -0800382 mActivity = (OpenExternalDirectoryActivity) getActivity();
Felipe Lemeb012f912016-01-22 16:49:55 -0800383 }
384
385 @Override
Felipe Lemeadccb992016-03-09 17:40:49 -0800386 public void onDestroyView() {
387 // Workaround for https://code.google.com/p/android/issues/detail?id=17423
388 if (mDialog != null && getRetainInstance()) {
389 mDialog.setDismissMessage(null);
390 }
391 super.onDestroyView();
392 }
393
394 @Override
Felipe Lemeb012f912016-01-22 16:49:55 -0800395 public Dialog onCreateDialog(Bundle savedInstanceState) {
Felipe Lemeadccb992016-03-09 17:40:49 -0800396 if (mDialog != null) {
397 if (DEBUG) Log.d(TAG, "fragment.onCreateDialog(): reusing dialog");
398 return mDialog;
399 }
400 if (mActivity != getActivity()) {
401 // Sanity check.
402 Log.wtf(TAG, "activity references don't match on onCreateDialog(): mActivity = "
403 + mActivity + " , getActivity() = " + getActivity());
404 mActivity = (OpenExternalDirectoryActivity) getActivity();
405 }
Felipe Leme3e166b22016-02-24 10:17:41 -0800406 final String directory = mFile.getName();
Felipe Lemedb892b82016-03-17 18:56:20 -0700407 final String directoryName = mIsRoot ? DIRECTORY_ROOT : directory;
Felipe Lemeadccb992016-03-09 17:40:49 -0800408 final Context context = mActivity.getApplicationContext();
Felipe Lemeb012f912016-01-22 16:49:55 -0800409 final OnClickListener listener = new OnClickListener() {
410
411 @Override
412 public void onClick(DialogInterface dialog, int which) {
413 Intent intent = null;
414 if (which == DialogInterface.BUTTON_POSITIVE) {
Felipe Leme3e166b22016-02-24 10:17:41 -0800415 intent = createGrantedUriPermissionsIntent(mActivity,
Felipe Leme560d23a2016-02-17 17:10:45 -0800416 mActivity.getExternalStorageClient(), mFile);
Felipe Lemeb012f912016-01-22 16:49:55 -0800417 }
418 if (which == DialogInterface.BUTTON_NEGATIVE || intent == null) {
Felipe Lemedb892b82016-03-17 18:56:20 -0700419 logValidScopedAccessRequest(mActivity, directoryName,
Felipe Leme3e166b22016-02-24 10:17:41 -0800420 SCOPED_DIRECTORY_ACCESS_DENIED);
Felipe Lemeadccb992016-03-09 17:40:49 -0800421 final boolean checked = mDontAskAgain.isChecked();
422 if (checked) {
423 logValidScopedAccessRequest(mActivity, directory,
424 SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST);
425 setScopedAccessPermissionStatus(context, mActivity.getCallingPackage(),
Felipe Lemedb892b82016-03-17 18:56:20 -0700426 mVolumeUuid, directoryName, PERMISSION_NEVER_ASK);
Felipe Lemeadccb992016-03-09 17:40:49 -0800427 } else {
428 setScopedAccessPermissionStatus(context, mActivity.getCallingPackage(),
Felipe Lemedb892b82016-03-17 18:56:20 -0700429 mVolumeUuid, directoryName, PERMISSION_ASK_AGAIN);
Felipe Lemeadccb992016-03-09 17:40:49 -0800430 }
431 mActivity.setResult(RESULT_CANCELED);
Felipe Lemeb012f912016-01-22 16:49:55 -0800432 } else {
Felipe Lemeadccb992016-03-09 17:40:49 -0800433 logValidScopedAccessRequest(mActivity, directory,
Felipe Leme3e166b22016-02-24 10:17:41 -0800434 SCOPED_DIRECTORY_ACCESS_GRANTED);
Felipe Lemeadccb992016-03-09 17:40:49 -0800435 mActivity.setResult(RESULT_OK, intent);
Felipe Lemeb012f912016-01-22 16:49:55 -0800436 }
Felipe Lemeadccb992016-03-09 17:40:49 -0800437 mActivity.finish();
Felipe Lemeb012f912016-01-22 16:49:55 -0800438 }
439 };
440
Felipe Lemeadccb992016-03-09 17:40:49 -0800441 @SuppressLint("InflateParams")
442 // It's ok pass null ViewRoot on AlertDialogs.
443 final View view = View.inflate(mActivity, R.layout.dialog_open_scoped_directory, null);
Felipe Lemedb892b82016-03-17 18:56:20 -0700444 final CharSequence message;
445 if (mIsRoot) {
446 message = TextUtils.expandTemplate(getText(
447 R.string.open_external_dialog_root_request), mAppLabel, mVolumeLabel);
448 } else {
Felipe Leme2ac87692016-03-29 19:06:02 -0700449 message = TextUtils.expandTemplate(
450 getText(mIsPrimary ? R.string.open_external_dialog_request_primary_volume
451 : R.string.open_external_dialog_request),
Felipe Lemedb892b82016-03-17 18:56:20 -0700452 mAppLabel, directory, mVolumeLabel);
453 }
Felipe Lemeadccb992016-03-09 17:40:49 -0800454 final TextView messageField = (TextView) view.findViewById(R.id.message);
455 messageField.setText(message);
Felipe Leme493611f2016-03-15 16:29:17 -0700456 mDialog = new AlertDialog.Builder(mActivity, R.style.Theme_AppCompat_Light_Dialog_Alert)
Felipe Lemeadccb992016-03-09 17:40:49 -0800457 .setView(view)
Felipe Lemeb012f912016-01-22 16:49:55 -0800458 .setPositiveButton(R.string.allow, listener)
459 .setNegativeButton(R.string.deny, listener)
460 .create();
Felipe Lemeadccb992016-03-09 17:40:49 -0800461
462 mDontAskAgain = (CheckBox) view.findViewById(R.id.do_not_ask_checkbox);
463 if (getScopedAccessPermissionStatus(context, mActivity.getCallingPackage(),
Felipe Lemedb892b82016-03-17 18:56:20 -0700464 mVolumeUuid, directoryName) == PERMISSION_ASK_AGAIN) {
Felipe Lemeadccb992016-03-09 17:40:49 -0800465 mDontAskAgain.setVisibility(View.VISIBLE);
466 mDontAskAgain.setOnCheckedChangeListener(new OnCheckedChangeListener() {
467
468 @Override
469 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
470 mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(!isChecked);
471 }
472 });
473 }
474
475 return mDialog;
Felipe Lemeb012f912016-01-22 16:49:55 -0800476 }
477
478 @Override
479 public void onCancel(DialogInterface dialog) {
480 super.onCancel(dialog);
481 final Activity activity = getActivity();
Felipe Leme3e166b22016-02-24 10:17:41 -0800482 logValidScopedAccessRequest(activity, mFile.getName(), SCOPED_DIRECTORY_ACCESS_DENIED);
Felipe Lemeb012f912016-01-22 16:49:55 -0800483 activity.setResult(RESULT_CANCELED);
484 activity.finish();
485 }
Felipe Leme560d23a2016-02-17 17:10:45 -0800486 }
Felipe Lemeb012f912016-01-22 16:49:55 -0800487
Felipe Leme560d23a2016-02-17 17:10:45 -0800488 private synchronized ContentProviderClient getExternalStorageClient() {
489 if (mExternalStorageClient == null) {
490 mExternalStorageClient =
491 getContentResolver().acquireContentProviderClient(EXTERNAL_STORAGE_AUTH);
Felipe Lemeb012f912016-01-22 16:49:55 -0800492 }
Felipe Leme560d23a2016-02-17 17:10:45 -0800493 return mExternalStorageClient;
Felipe Lemeb012f912016-01-22 16:49:55 -0800494 }
495}