blob: 74af224b5990f620dc39c38e98fa7ade701b922e [file] [log] [blame]
Felipe Lemea4f4d7e2016-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 Leme5228bd02016-02-17 10:12:04 -080020import static android.os.storage.StorageVolume.EXTRA_DIRECTORY_NAME;
21import static android.os.storage.StorageVolume.EXTRA_STORAGE_VOLUME;
Felipe Leme0bdb7c32016-03-09 17:40:49 -080022import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_ALREADY_DENIED;
Felipe Lemee4b60122016-02-24 10:17:41 -080023import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED;
24import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_DENIED;
Felipe Leme0bdb7c32016-03-09 17:40:49 -080025import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST;
Felipe Lemee4b60122016-02-24 10:17:41 -080026import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_ERROR;
27import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_GRANTED;
28import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS;
29import static com.android.documentsui.Metrics.SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY;
Felipe Leme0bdb7c32016-03-09 17:40:49 -080030import static com.android.documentsui.Metrics.logInvalidScopedAccessRequest;
31import static com.android.documentsui.Metrics.logValidScopedAccessRequest;
Steve McKayd9caa6a2016-09-15 16:36:45 -070032import static com.android.documentsui.base.Shared.DEBUG;
Steve McKaye8b547f2016-12-19 14:42:55 -080033import static com.android.documentsui.prefs.LocalPreferences.PERMISSION_ASK_AGAIN;
34import static com.android.documentsui.prefs.LocalPreferences.PERMISSION_NEVER_ASK;
35import static com.android.documentsui.prefs.LocalPreferences.getScopedAccessPermissionStatus;
36import static com.android.documentsui.prefs.LocalPreferences.setScopedAccessPermissionStatus;
Felipe Lemea4f4d7e2016-01-22 16:49:55 -080037
Felipe Leme0bdb7c32016-03-09 17:40:49 -080038import android.annotation.SuppressLint;
Felipe Lemea4f4d7e2016-01-22 16:49:55 -080039import android.app.Activity;
Felipe Leme8cf6ce22016-02-17 17:10:45 -080040import android.app.ActivityManager;
Felipe Lemea4f4d7e2016-01-22 16:49:55 -080041import android.app.AlertDialog;
42import android.app.Dialog;
43import android.app.DialogFragment;
44import android.app.FragmentManager;
45import android.app.FragmentTransaction;
Felipe Lemea4f4d7e2016-01-22 16:49:55 -080046import android.content.ContentProviderClient;
Felipe Lemea4f4d7e2016-01-22 16:49:55 -080047import android.content.Context;
48import android.content.DialogInterface;
49import android.content.DialogInterface.OnClickListener;
50import android.content.Intent;
Felipe Leme8cf6ce22016-02-17 17:10:45 -080051import android.content.UriPermission;
Felipe Lemea4f4d7e2016-01-22 16:49:55 -080052import android.content.pm.PackageManager;
53import android.content.pm.PackageManager.NameNotFoundException;
54import android.net.Uri;
55import android.os.Bundle;
Felipe Leme5228bd02016-02-17 10:12:04 -080056import android.os.Parcelable;
Felipe Lemea4f4d7e2016-01-22 16:49:55 -080057import android.os.RemoteException;
58import android.os.UserHandle;
59import android.os.storage.StorageManager;
Felipe Leme5228bd02016-02-17 10:12:04 -080060import android.os.storage.StorageVolume;
Felipe Lemea4f4d7e2016-01-22 16:49:55 -080061import android.os.storage.VolumeInfo;
62import android.provider.DocumentsContract;
63import android.text.TextUtils;
64import android.util.Log;
Felipe Leme0bdb7c32016-03-09 17:40:49 -080065import android.view.View;
66import android.widget.CheckBox;
67import android.widget.CompoundButton;
68import android.widget.CompoundButton.OnCheckedChangeListener;
69import android.widget.TextView;
Felipe Lemea4f4d7e2016-01-22 16:49:55 -080070
Steve McKay8659cbc2016-10-31 13:13:36 -070071import com.android.documentsui.base.Providers;
72
Ben Kwaf4b0ff62016-02-02 12:11:10 -080073import java.io.File;
74import java.io.IOException;
75import java.util.List;
76
Felipe Lemea4f4d7e2016-01-22 16:49:55 -080077/**
78 * Activity responsible for handling {@link Intent#ACTION_OPEN_EXTERNAL_DOCUMENT}.
79 */
80public class OpenExternalDirectoryActivity extends Activity {
Ben Kwaf4b0ff62016-02-02 12:11:10 -080081 private static final String TAG = "OpenExternalDirectory";
Felipe Lemea4f4d7e2016-01-22 16:49:55 -080082 private static final String FM_TAG = "open_external_directory";
Felipe Lemea4f4d7e2016-01-22 16:49:55 -080083 private static final String EXTRA_FILE = "com.android.documentsui.FILE";
84 private static final String EXTRA_APP_LABEL = "com.android.documentsui.APP_LABEL";
85 private static final String EXTRA_VOLUME_LABEL = "com.android.documentsui.VOLUME_LABEL";
Felipe Leme0bdb7c32016-03-09 17:40:49 -080086 private static final String EXTRA_VOLUME_UUID = "com.android.documentsui.VOLUME_UUID";
Felipe Lemeb59a8a62016-03-17 18:56:20 -070087 private static final String EXTRA_IS_ROOT = "com.android.documentsui.IS_ROOT";
Felipe Leme8216cea2016-03-29 19:06:02 -070088 private static final String EXTRA_IS_PRIMARY = "com.android.documentsui.IS_PRIMARY";
Felipe Lemeb59a8a62016-03-17 18:56:20 -070089 // Special directory name representing the full volume
90 static final String DIRECTORY_ROOT = "ROOT_DIRECTORY";
Felipe Lemea4f4d7e2016-01-22 16:49:55 -080091
Felipe Leme8cf6ce22016-02-17 17:10:45 -080092 private ContentProviderClient mExternalStorageClient;
93
Felipe Lemea4f4d7e2016-01-22 16:49:55 -080094 @Override
95 public void onCreate(Bundle savedInstanceState) {
96 super.onCreate(savedInstanceState);
Felipe Leme0bdb7c32016-03-09 17:40:49 -080097 if (savedInstanceState != null) {
98 if (DEBUG) Log.d(TAG, "activity.onCreateDialog(): reusing instance");
99 return;
100 }
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800101
102 final Intent intent = getIntent();
Felipe Leme5228bd02016-02-17 10:12:04 -0800103 if (intent == null) {
104 if (DEBUG) Log.d(TAG, "missing intent");
Felipe Lemee4b60122016-02-24 10:17:41 -0800105 logInvalidScopedAccessRequest(this, SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS);
Felipe Leme5228bd02016-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 Lemee4b60122016-02-24 10:17:41 -0800115 logInvalidScopedAccessRequest(this, SCOPED_DIRECTORY_ACCESS_INVALID_ARGUMENTS);
Felipe Leme5228bd02016-02-17 10:12:04 -0800116 setResult(RESULT_CANCELED);
117 finish();
118 return;
119 }
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700120 String directoryName = intent.getStringExtra(EXTRA_DIRECTORY_NAME );
Felipe Leme5228bd02016-02-17 10:12:04 -0800121 if (directoryName == null) {
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700122 directoryName = DIRECTORY_ROOT;
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800123 }
Felipe Leme0bdb7c32016-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 Lemea4f4d7e2016-01-22 16:49:55 -0800133
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800134 final int userId = UserHandle.myUserId();
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800135 if (!showFragment(this, userId, volume, directoryName)) {
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800136 setResult(RESULT_CANCELED);
137 finish();
138 return;
139 }
140 }
141
Felipe Leme8cf6ce22016-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 Lemea4f4d7e2016-01-22 16:49:55 -0800150 /**
Felipe Leme5228bd02016-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 Lemea4f4d7e2016-01-22 16:49:55 -0800153 */
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800154 private static boolean showFragment(OpenExternalDirectoryActivity activity, int userId,
155 StorageVolume storageVolume, String directoryName) {
Felipe Leme5228bd02016-02-17 10:12:04 -0800156 if (DEBUG)
157 Log.d(TAG, "showFragment() for volume " + storageVolume.dump() + ", directory "
158 + directoryName + ", and user " + userId);
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700159 final boolean isRoot = directoryName.equals(DIRECTORY_ROOT);
Felipe Leme8216cea2016-03-29 19:06:02 -0700160 final boolean isPrimary = storageVolume.isPrimary();
161
162 if (isRoot && isPrimary) {
163 if (DEBUG) Log.d(TAG, "root access requested on primary volume");
164 return false;
165 }
166
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700167 final File volumeRoot = storageVolume.getPathFile();
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800168 File file;
169 try {
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700170 file = isRoot ? volumeRoot : new File(volumeRoot, directoryName).getCanonicalFile();
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800171 } catch (IOException e) {
Felipe Leme5228bd02016-02-17 10:12:04 -0800172 Log.e(TAG, "Could not get canonical file for volume " + storageVolume.dump()
173 + " and directory " + directoryName);
Felipe Lemee4b60122016-02-24 10:17:41 -0800174 logInvalidScopedAccessRequest(activity, SCOPED_DIRECTORY_ACCESS_ERROR);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800175 return false;
176 }
177 final StorageManager sm =
178 (StorageManager) activity.getSystemService(Context.STORAGE_SERVICE);
179
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700180 final String root, directory;
181 if (isRoot) {
182 root = volumeRoot.getAbsolutePath();
183 directory = ".";
184 } else {
185 root = file.getParent();
186 directory = file.getName();
187 // Verify directory is valid.
188 if (TextUtils.isEmpty(directory) || !isStandardDirectory(directory)) {
189 if (DEBUG)
190 Log.d(TAG, "Directory '" + directory + "' is not standard (full path: '"
191 + file.getAbsolutePath() + "')");
192 logInvalidScopedAccessRequest(activity, SCOPED_DIRECTORY_ACCESS_INVALID_DIRECTORY);
193 return false;
194 }
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800195 }
196
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800197 // Gets volume label and converted path.
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800198 String volumeLabel = null;
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800199 String volumeUuid = null;
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800200 final List<VolumeInfo> volumes = sm.getVolumes();
201 if (DEBUG) Log.d(TAG, "Number of volumes: " + volumes.size());
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700202 File internalRoot = null;
Felipe Leme3ee56ca2016-06-09 15:25:00 -0700203 boolean found = true;
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800204 for (VolumeInfo volume : volumes) {
205 if (isRightVolume(volume, root, userId)) {
Felipe Leme3ee56ca2016-06-09 15:25:00 -0700206 found = true;
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700207 internalRoot = volume.getInternalPathForUser(userId);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800208 // Must convert path before calling getDocIdForFileCreateNewDir()
209 if (DEBUG) Log.d(TAG, "Converting " + root + " to " + internalRoot);
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700210 file = isRoot ? internalRoot : new File(internalRoot, directory);
Felipe Leme3ee56ca2016-06-09 15:25:00 -0700211 volumeUuid = storageVolume.getUuid();
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800212 volumeLabel = sm.getBestVolumeDescription(volume);
Felipe Leme3ee56ca2016-06-09 15:25:00 -0700213 if (TextUtils.isEmpty(volumeLabel)) {
214 volumeLabel = storageVolume.getDescription(activity);
215 }
216 if (TextUtils.isEmpty(volumeLabel)) {
217 volumeLabel = activity.getString(android.R.string.unknownName);
218 Log.w(TAG, "No volume description for " + volume + "; using " + volumeLabel);
219 }
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800220 break;
221 }
222 }
Felipe Lemee71de812016-04-18 14:55:15 -0700223 if (internalRoot == null) {
224 // Should not happen on normal circumstances, unless app crafted an invalid volume
225 // using reflection or the list of mounted volumes changed.
226 Log.e(TAG, "Didn't find right volume for '" + storageVolume.dump() + "' on " + volumes);
227 return false;
228 }
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800229
230 // Checks if the user has granted the permission already.
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700231 final Intent intent = getIntentForExistingPermission(activity, isRoot, internalRoot, file);
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800232 if (intent != null) {
Felipe Lemee4b60122016-02-24 10:17:41 -0800233 logValidScopedAccessRequest(activity, directory,
234 SCOPED_DIRECTORY_ACCESS_ALREADY_GRANTED);
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800235 activity.setResult(RESULT_OK, intent);
236 activity.finish();
237 return true;
238 }
239
Felipe Leme3ee56ca2016-06-09 15:25:00 -0700240 if (!found) {
Felipe Leme5228bd02016-02-17 10:12:04 -0800241 Log.e(TAG, "Could not get volume for " + file);
Felipe Lemee4b60122016-02-24 10:17:41 -0800242 logInvalidScopedAccessRequest(activity, SCOPED_DIRECTORY_ACCESS_ERROR);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800243 return false;
244 }
245
246 // Gets the package label.
247 final String appLabel = getAppLabel(activity);
248 if (appLabel == null) {
Felipe Lemee4b60122016-02-24 10:17:41 -0800249 // Error already logged.
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800250 return false;
251 }
252
253 // Sets args that will be retrieve on onCreate()
254 final Bundle args = new Bundle();
255 args.putString(EXTRA_FILE, file.getAbsolutePath());
256 args.putString(EXTRA_VOLUME_LABEL, volumeLabel);
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800257 args.putString(EXTRA_VOLUME_UUID, volumeUuid);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800258 args.putString(EXTRA_APP_LABEL, appLabel);
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700259 args.putBoolean(EXTRA_IS_ROOT, isRoot);
Felipe Leme8216cea2016-03-29 19:06:02 -0700260 args.putBoolean(EXTRA_IS_PRIMARY, isPrimary);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800261
262 final FragmentManager fm = activity.getFragmentManager();
263 final FragmentTransaction ft = fm.beginTransaction();
264 final OpenExternalDirectoryDialogFragment fragment =
265 new OpenExternalDirectoryDialogFragment();
266 fragment.setArguments(args);
267 ft.add(fragment, FM_TAG);
268 ft.commitAllowingStateLoss();
269
270 return true;
271 }
272
273 private static String getAppLabel(Activity activity) {
274 final String packageName = activity.getCallingPackage();
275 final PackageManager pm = activity.getPackageManager();
276 try {
277 return pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)).toString();
278 } catch (NameNotFoundException e) {
Felipe Lemee4b60122016-02-24 10:17:41 -0800279 logInvalidScopedAccessRequest(activity, SCOPED_DIRECTORY_ACCESS_ERROR);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800280 Log.w(TAG, "Could not get label for package " + packageName);
281 return null;
282 }
283 }
284
285 private static boolean isRightVolume(VolumeInfo volume, String root, int userId) {
286 final File userPath = volume.getPathForUser(userId);
287 final String path = userPath == null ? null : volume.getPathForUser(userId).getPath();
Felipe Lemefffd8fc2016-06-10 11:30:56 -0700288 final boolean isMounted = volume.isMountedReadable();
Felipe Leme5228bd02016-02-17 10:12:04 -0800289 if (DEBUG)
Felipe Lemefffd8fc2016-06-10 11:30:56 -0700290 Log.d(TAG, "Volume: " + volume
291 + "\n\tuserId: " + userId
292 + "\n\tuserPath: " + userPath
293 + "\n\troot: " + root
294 + "\n\tpath: " + path
295 + "\n\tisMounted: " + isMounted);
Felipe Leme5228bd02016-02-17 10:12:04 -0800296
Felipe Lemefffd8fc2016-06-10 11:30:56 -0700297 return isMounted && root.equals(path);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800298 }
299
Felipe Lemee4b60122016-02-24 10:17:41 -0800300 private static Uri getGrantedUriPermission(Context context, ContentProviderClient provider,
301 File file) {
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800302 // Calls ExternalStorageProvider to get the doc id for the file
303 final Bundle bundle;
304 try {
305 bundle = provider.call("getDocIdForFileCreateNewDir", file.getPath(), null);
306 } catch (RemoteException e) {
307 Log.e(TAG, "Did not get doc id from External Storage provider for " + file, e);
Felipe Lemee4b60122016-02-24 10:17:41 -0800308 logInvalidScopedAccessRequest(context, SCOPED_DIRECTORY_ACCESS_ERROR);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800309 return null;
310 }
311 final String docId = bundle == null ? null : bundle.getString("DOC_ID");
312 if (docId == null) {
313 Log.e(TAG, "Did not get doc id from External Storage provider for " + file);
Felipe Lemee4b60122016-02-24 10:17:41 -0800314 logInvalidScopedAccessRequest(context, SCOPED_DIRECTORY_ACCESS_ERROR);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800315 return null;
316 }
Ben Kwa543a2922016-03-22 11:11:46 -0700317 if (DEBUG) Log.d(TAG, "doc id for " + file + ": " + docId);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800318
Steve McKay8659cbc2016-10-31 13:13:36 -0700319 final Uri uri = DocumentsContract.buildTreeDocumentUri(Providers.AUTHORITY_STORAGE, docId);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800320 if (uri == null) {
321 Log.e(TAG, "Could not get URI for doc id " + docId);
322 return null;
323 }
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800324 if (DEBUG) Log.d(TAG, "URI for " + file + ": " + uri);
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800325 return uri;
326 }
327
Felipe Lemee4b60122016-02-24 10:17:41 -0800328 private static Intent createGrantedUriPermissionsIntent(Context context,
329 ContentProviderClient provider, File file) {
330 final Uri uri = getGrantedUriPermission(context, provider, file);
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800331 return createGrantedUriPermissionsIntent(uri);
332 }
333
334 private static Intent createGrantedUriPermissionsIntent(Uri uri) {
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800335 final Intent intent = new Intent();
336 intent.setData(uri);
337 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
338 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
339 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
340 | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
341 return intent;
342 }
343
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800344 private static Intent getIntentForExistingPermission(OpenExternalDirectoryActivity activity,
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700345 boolean isRoot, File root, File file) {
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800346 final String packageName = activity.getCallingPackage();
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700347 final ContentProviderClient storageClient = activity.getExternalStorageClient();
348 final Uri grantedUri = getGrantedUriPermission(activity, storageClient, file);
349 final Uri rootUri = root.equals(file) ? grantedUri
350 : getGrantedUriPermission(activity, storageClient, root);
351
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800352 if (DEBUG)
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700353 Log.d(TAG, "checking if " + packageName + " already has permission for " + grantedUri
354 + " or its root (" + rootUri + ")");
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800355 final ActivityManager am =
356 (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
357 for (UriPermission uriPermission : am.getGrantedUriPermissions(packageName).getList()) {
358 final Uri uri = uriPermission.getUri();
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700359 if (uri == null) {
360 Log.w(TAG, "null URI for " + uriPermission);
361 continue;
362 }
363 if (uri.equals(grantedUri) || uri.equals(rootUri)) {
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800364 if (DEBUG) Log.d(TAG, packageName + " already has permission: " + uriPermission);
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700365 return createGrantedUriPermissionsIntent(grantedUri);
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800366 }
367 }
368 if (DEBUG) Log.d(TAG, packageName + " does not have permission for " + grantedUri);
369 return null;
370 }
371
Ben Kwaf4b0ff62016-02-02 12:11:10 -0800372 public static class OpenExternalDirectoryDialogFragment extends DialogFragment {
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800373
374 private File mFile;
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800375 private String mVolumeUuid;
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800376 private String mVolumeLabel;
377 private String mAppLabel;
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700378 private boolean mIsRoot;
Felipe Leme8216cea2016-03-29 19:06:02 -0700379 private boolean mIsPrimary;
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800380 private CheckBox mDontAskAgain;
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800381 private OpenExternalDirectoryActivity mActivity;
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800382 private AlertDialog mDialog;
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800383
384 @Override
385 public void onCreate(Bundle savedInstanceState) {
386 super.onCreate(savedInstanceState);
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800387 setRetainInstance(true);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800388 final Bundle args = getArguments();
389 if (args != null) {
390 mFile = new File(args.getString(EXTRA_FILE));
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800391 mVolumeUuid = args.getString(EXTRA_VOLUME_UUID);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800392 mVolumeLabel = args.getString(EXTRA_VOLUME_LABEL);
393 mAppLabel = args.getString(EXTRA_APP_LABEL);
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700394 mIsRoot = args.getBoolean(EXTRA_IS_ROOT);
Felipe Leme8216cea2016-03-29 19:06:02 -0700395 mIsPrimary= args.getBoolean(EXTRA_IS_PRIMARY);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800396 }
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800397 mActivity = (OpenExternalDirectoryActivity) getActivity();
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800398 }
399
400 @Override
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800401 public void onDestroyView() {
402 // Workaround for https://code.google.com/p/android/issues/detail?id=17423
403 if (mDialog != null && getRetainInstance()) {
404 mDialog.setDismissMessage(null);
405 }
406 super.onDestroyView();
407 }
408
409 @Override
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800410 public Dialog onCreateDialog(Bundle savedInstanceState) {
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800411 if (mDialog != null) {
412 if (DEBUG) Log.d(TAG, "fragment.onCreateDialog(): reusing dialog");
413 return mDialog;
414 }
415 if (mActivity != getActivity()) {
416 // Sanity check.
417 Log.wtf(TAG, "activity references don't match on onCreateDialog(): mActivity = "
418 + mActivity + " , getActivity() = " + getActivity());
419 mActivity = (OpenExternalDirectoryActivity) getActivity();
420 }
Felipe Lemee4b60122016-02-24 10:17:41 -0800421 final String directory = mFile.getName();
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700422 final String directoryName = mIsRoot ? DIRECTORY_ROOT : directory;
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800423 final Context context = mActivity.getApplicationContext();
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800424 final OnClickListener listener = new OnClickListener() {
425
426 @Override
427 public void onClick(DialogInterface dialog, int which) {
428 Intent intent = null;
429 if (which == DialogInterface.BUTTON_POSITIVE) {
Felipe Lemee4b60122016-02-24 10:17:41 -0800430 intent = createGrantedUriPermissionsIntent(mActivity,
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800431 mActivity.getExternalStorageClient(), mFile);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800432 }
433 if (which == DialogInterface.BUTTON_NEGATIVE || intent == null) {
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700434 logValidScopedAccessRequest(mActivity, directoryName,
Felipe Lemee4b60122016-02-24 10:17:41 -0800435 SCOPED_DIRECTORY_ACCESS_DENIED);
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800436 final boolean checked = mDontAskAgain.isChecked();
437 if (checked) {
438 logValidScopedAccessRequest(mActivity, directory,
439 SCOPED_DIRECTORY_ACCESS_DENIED_AND_PERSIST);
440 setScopedAccessPermissionStatus(context, mActivity.getCallingPackage(),
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700441 mVolumeUuid, directoryName, PERMISSION_NEVER_ASK);
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800442 } else {
443 setScopedAccessPermissionStatus(context, mActivity.getCallingPackage(),
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700444 mVolumeUuid, directoryName, PERMISSION_ASK_AGAIN);
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800445 }
446 mActivity.setResult(RESULT_CANCELED);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800447 } else {
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800448 logValidScopedAccessRequest(mActivity, directory,
Felipe Lemee4b60122016-02-24 10:17:41 -0800449 SCOPED_DIRECTORY_ACCESS_GRANTED);
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800450 mActivity.setResult(RESULT_OK, intent);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800451 }
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800452 mActivity.finish();
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800453 }
454 };
455
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800456 @SuppressLint("InflateParams")
457 // It's ok pass null ViewRoot on AlertDialogs.
458 final View view = View.inflate(mActivity, R.layout.dialog_open_scoped_directory, null);
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700459 final CharSequence message;
460 if (mIsRoot) {
461 message = TextUtils.expandTemplate(getText(
462 R.string.open_external_dialog_root_request), mAppLabel, mVolumeLabel);
463 } else {
Felipe Leme8216cea2016-03-29 19:06:02 -0700464 message = TextUtils.expandTemplate(
465 getText(mIsPrimary ? R.string.open_external_dialog_request_primary_volume
466 : R.string.open_external_dialog_request),
Felipe Leme3ee56ca2016-06-09 15:25:00 -0700467 mAppLabel, directory, mVolumeLabel);
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700468 }
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800469 final TextView messageField = (TextView) view.findViewById(R.id.message);
470 messageField.setText(message);
Felipe Lemecb5e3e72016-03-15 16:29:17 -0700471 mDialog = new AlertDialog.Builder(mActivity, R.style.Theme_AppCompat_Light_Dialog_Alert)
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800472 .setView(view)
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800473 .setPositiveButton(R.string.allow, listener)
474 .setNegativeButton(R.string.deny, listener)
475 .create();
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800476
477 mDontAskAgain = (CheckBox) view.findViewById(R.id.do_not_ask_checkbox);
478 if (getScopedAccessPermissionStatus(context, mActivity.getCallingPackage(),
Felipe Lemeb59a8a62016-03-17 18:56:20 -0700479 mVolumeUuid, directoryName) == PERMISSION_ASK_AGAIN) {
Felipe Leme0bdb7c32016-03-09 17:40:49 -0800480 mDontAskAgain.setVisibility(View.VISIBLE);
481 mDontAskAgain.setOnCheckedChangeListener(new OnCheckedChangeListener() {
482
483 @Override
484 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
485 mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(!isChecked);
486 }
487 });
488 }
489
490 return mDialog;
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800491 }
492
493 @Override
494 public void onCancel(DialogInterface dialog) {
495 super.onCancel(dialog);
496 final Activity activity = getActivity();
Felipe Lemee4b60122016-02-24 10:17:41 -0800497 logValidScopedAccessRequest(activity, mFile.getName(), SCOPED_DIRECTORY_ACCESS_DENIED);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800498 activity.setResult(RESULT_CANCELED);
499 activity.finish();
500 }
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800501 }
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800502
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800503 private synchronized ContentProviderClient getExternalStorageClient() {
504 if (mExternalStorageClient == null) {
505 mExternalStorageClient =
Steve McKay8659cbc2016-10-31 13:13:36 -0700506 getContentResolver().acquireContentProviderClient(Providers.AUTHORITY_STORAGE);
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800507 }
Felipe Leme8cf6ce22016-02-17 17:10:45 -0800508 return mExternalStorageClient;
Felipe Lemea4f4d7e2016-01-22 16:49:55 -0800509 }
510}