OSDN Git Service

OPP: Initialize OPP FileProvider after user unlock
authorJack He <siyuanh@google.com>
Sat, 15 Apr 2017 00:06:16 +0000 (17:06 -0700)
committerAndre Eisenbach <eisenbach@google.com>
Tue, 18 Apr 2017 00:41:32 +0000 (00:41 +0000)
* The FileProvider used by Bluetooth OPP profile used to be initialized
  before user unlock, resulting in file system access errors since
  credential encrypted volumes are not mounted and unlocked yet
* Incomplete initialization also leads to NPE when getUriForFile is
  called
* This change adds BluetoothOppProvider that only initializes after user
  unlocks the device

Bug: 36274847
Test: make, Opp regression test
Change-Id: I323b9084de1ff72922c4dd9866c36dd172696a8f
(cherry picked from commit a19f73959d7e054b206633aa1592a874d0d623f2)

AndroidManifest.xml
src/com/android/bluetooth/opp/BluetoothOppFileProvider.java [new file with mode: 0644]
src/com/android/bluetooth/opp/BluetoothOppUtility.java

index a922232..1b46ff9 100644 (file)
@@ -87,8 +87,8 @@
                     android:pathPrefix="/btopp"
                     android:permission="android.permission.ACCESS_BLUETOOTH_SHARE" />
         </provider>
-        <provider android:name="android.support.v4.content.FileProvider"
-            android:authorities="com.google.android.bluetooth.fileprovider"
+        <provider android:name=".opp.BluetoothOppFileProvider"
+            android:authorities="com.android.bluetooth.opp.fileprovider"
             android:grantUriPermissions="true"
             android:exported="false">
             <meta-data
diff --git a/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java b/src/com/android/bluetooth/opp/BluetoothOppFileProvider.java
new file mode 100644 (file)
index 0000000..3765210
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.opp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ProviderInfo;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.support.v4.content.FileProvider;
+import android.util.Log;
+import java.io.File;
+
+/**
+ * A FileProvider for files received by Bluetooth share
+ */
+public class BluetoothOppFileProvider extends FileProvider {
+    private static final String TAG = "BluetoothOppFileProvider";
+
+    private Context mContext = null;
+    private ProviderInfo mProviderInfo = null;
+    private boolean mRegisteredReceiver = false;
+    private boolean mInitialized = false;
+
+    /** Broadcast receiver that attach FileProvider info when user unlocks the phone for the
+     *  first time after reboot and the credential-encrypted storage is available.
+     */
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(final Context context, Intent intent) {
+            if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
+                attachInfo(mContext, mProviderInfo);
+            }
+        }
+    };
+
+    /**
+     * After the FileProvider is instantiated, this method is called to provide the system with
+     * information about the provider. The actual initialization is delayed until user unlock the
+     * device
+     *
+     * @param context A {@link Context} for the current component.
+     * @param info A {@link ProviderInfo} for the new provider.
+     */
+    @Override
+    public void attachInfo(Context context, ProviderInfo info) {
+        synchronized (this) {
+            mContext = context;
+            mProviderInfo = info;
+            if (!mRegisteredReceiver) {
+                IntentFilter userFilter = new IntentFilter();
+                userFilter.addAction(Intent.ACTION_USER_UNLOCKED);
+                mContext.registerReceiverAsUser(
+                        mBroadcastReceiver, UserHandle.CURRENT, userFilter, null, null);
+                mRegisteredReceiver = true;
+            }
+            UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+            if (userManager.isUserUnlocked()) {
+                if (!mInitialized) {
+                    if (Constants.DEBUG) Log.d(TAG, "Initialized");
+                    super.attachInfo(mContext, mProviderInfo);
+                    mInitialized = true;
+                }
+                if (mRegisteredReceiver) {
+                    mContext.unregisterReceiver(mBroadcastReceiver);
+                    mRegisteredReceiver = false;
+                }
+            }
+        }
+    }
+
+    /**
+     * Return a content URI for a given {@link File}. Specific temporary
+     * permissions for the content URI can be set with
+     * {@link Context#grantUriPermission(String, Uri, int)}, or added
+     * to an {@link Intent} by calling {@link Intent#setData(Uri) setData()} and then
+     * {@link Intent#setFlags(int) setFlags()}; in both cases, the applicable flags are
+     * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and
+     * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. A FileProvider can only return a
+     * <code>content</code> {@link Uri} for file paths defined in their <code>&lt;paths&gt;</code>
+     * meta-data element. See the Class Overview for more information.
+     *
+     * @param context A {@link Context} for the current component.
+     * @param authority The authority of a {@link FileProvider} defined in a
+     *            {@code <provider>} element in your app's manifest.
+     * @param file A {@link File} pointing to the filename for which you want a
+     * <code>content</code> {@link Uri}.
+     * @return A content URI for the file. Null if the user hasn't unlock the phone
+     * @throws IllegalArgumentException When the given {@link File} is outside
+     * the paths supported by the provider.
+     */
+    public static Uri getUriForFile(Context context, String authority, File file) {
+        UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+        if (!userManager.isUserUnlocked()) {
+            return null;
+        }
+        context = context.createCredentialProtectedStorageContext();
+        return FileProvider.getUriForFile(context, authority, file);
+    }
+}
index 2659475..11f9324 100644 (file)
@@ -55,7 +55,6 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 
-import android.support.v4.content.FileProvider;
 /**
  * This class has some utilities for Opp application;
  */
@@ -201,8 +200,12 @@ public class BluetoothOppUtility {
             return;
         }
 
-        Uri path = FileProvider.getUriForFile(context,
-                       "com.google.android.bluetooth.fileprovider", f);
+        Uri path = BluetoothOppFileProvider.getUriForFile(
+                context, "com.android.bluetooth.opp.fileprovider", f);
+        if (path == null) {
+            Log.w(TAG, "Cannot get content URI for the shared file");
+            return;
+        }
         // If there is no scheme, then it must be a file
         if (path.getScheme() == null) {
             path = Uri.fromFile(new File(fileName));