Browser: runtime permissions

FPIIM-951

Change-Id: I19caa970421898196c005aa9ad7cc651cd574c0a
Signed-off-by: jrizzoli <joey@cyanogenmoditalia.it>
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a1de131..67b4b40 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -20,6 +20,9 @@
 
     <original-package android:name="com.android.browser" />
 
+    <uses-sdk android:minSdkVersion="21"
+        android:targetSdkVersion="23" />
+
     <permission android:name="com.android.browser.permission.PRELOAD"
         android:label="@string/permission_preload_label"
         android:protectionLevel="signatureOrSystem" />
@@ -271,4 +274,3 @@
     </application>
 
 </manifest>
-
diff --git a/res/values/strings.xml b/res/values/strings.xml
index bba4dbe..816aecd 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -4,9 +4,9 @@
      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.
@@ -175,7 +175,7 @@
     <string name="bookmark_cannot_save_url">This URL can\'t be bookmarked.</string>
     <!-- Menu item in the page that displays all bookmarks. It brings up a
             dialog that allows the user to bookmark the page that the browser is
-            currently on, but is not visible because the bookmarks page is 
+            currently on, but is not visible because the bookmarks page is
             showing. -->
     <string name="bookmark_page">Bookmark last-viewed page</string>
     <!-- Menu item in the page that displays all bookmarks.  Switches to
@@ -265,7 +265,7 @@
     <!-- Context Menu item to show the currently selected address in the Maps
             application -->
     <string name="contextmenu_map">Map</string>
- 
+
      <!-- Title of the dialog used for selecting the application that should be
             used for sharing a link (e.g. Gmail or another app). See also
             contextmenu_sharelink above -->
@@ -722,7 +722,7 @@
     <!-- Verb placed in front of a screenshot of a web page that, when clicked,
             will add that page to bookmarks -->
     <string name="add_bookmark_short">Add</string>
-    
+
     <!-- This string is for the browser "Go To" UI. -->
     <!-- Do not translate.  This string is not displayed in UI (no badge is selected for go to). -->
     <string name="search_label" translatable="false">Browser</string>
@@ -842,7 +842,7 @@
     <string name="webstorage_outofspace_notification_text">Touch to free up space.</string>
     <!-- Used in the Browser Settings -->
     <string name="webstorage_clear_data_title">Clear stored data</string>
-    
+
     <!-- Confirmation dialog when the user ask to clear all data for an origin -->
     <string name="webstorage_clear_data_dialog_message">Delete all data stored by this website?</string>
     <string name="webstorage_clear_data_dialog_ok_button">OK</string>
@@ -1020,4 +1020,11 @@
     <!-- Content description for navigating up in the bookmark folder hierarchy [CHAR LIMIT=NONE] -->
     <string name="accessibility_button_bookmarks_folder_up">Previous folder</string>
 
+    <!-- Permission check -->
+    <string name="permission_not_granted_dialog_title">Permission not granted</string>
+    <string name="permission_not_granted_dialog_message">The permission to write to external storage was not granted. Cannot download.</string>
+    <string name="browser_error_title">Browser error</string>
+    <string name="dialog_dismiss">Dismiss</string>
+    <string name="error_permissions">The app does not have critical permissions needed to run. Please check your permissions settings.</string>
+
 </resources>
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index 4166b11..85af8af 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -17,12 +17,15 @@
 package com.android.browser;
 
 import android.app.Activity;
+import android.app.AlertDialog;
 import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.PowerManager;
+import android.support.v4.app.ActivityCompat;
 import android.util.Log;
 import android.view.ActionMode;
 import android.view.ContextMenu;
@@ -37,7 +40,8 @@
 import com.android.browser.stub.NullController;
 import com.google.common.annotations.VisibleForTesting;
 
-public class BrowserActivity extends Activity {
+public class BrowserActivity extends Activity
+        implements ActivityCompat.OnRequestPermissionsResultCallback {
 
     public static final String ACTION_SHOW_BOOKMARKS = "show_bookmarks";
     public static final String ACTION_SHOW_BROWSER = "show_browser";
@@ -51,6 +55,8 @@
 
     private ActivityController mController = NullController.INSTANCE;
 
+    public static final int PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
+
     @Override
     public void onCreate(Bundle icicle) {
         if (LOGV_ENABLED) {
@@ -304,4 +310,25 @@
                 super.dispatchGenericMotionEvent(ev);
     }
 
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, String[] permissions,
+            int[] grantResults) {
+        switch (requestCode) {
+            case PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE:
+                // If request is cancelled, the result array is empty
+                if (grantResults.length > 0 &&
+                        grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+                    DownloadHandler.checkPendingDownloads(this);
+                } else {
+                    new AlertDialog.Builder(this)
+                            .setTitle(R.string.permission_not_granted_dialog_title)
+                            .setMessage(R.string.permission_not_granted_dialog_message)
+                            .setPositiveButton(android.R.string.ok, null)
+                            .show();
+                }
+                break;
+        }
+    }
+
 }
diff --git a/src/com/android/browser/DownloadHandler.java b/src/com/android/browser/DownloadHandler.java
index 208d4ce..84e67c0 100644
--- a/src/com/android/browser/DownloadHandler.java
+++ b/src/com/android/browser/DownloadHandler.java
@@ -28,6 +28,8 @@
 import android.net.Uri;
 import android.net.WebAddress;
 import android.os.Environment;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
 import android.text.TextUtils;
 import android.util.Log;
 import android.webkit.CookieManager;
@@ -44,6 +46,46 @@
 
     private static final String LOGTAG = "DLHandler";
 
+    //Singleton to hold the information about any pending downloads
+    private static PendingDownloadBundle pendingDownloadBundle;
+
+    private static class PendingDownloadBundle {
+        String url;
+        String userAgent;
+        String contentDisposition;
+        String mimetype;
+        String referer;
+        boolean privateBrowsing;
+
+        public static PendingDownloadBundle create(String url, String userAgent,
+                String contentDisposition, String mimetype, String referer,
+                boolean privateBrowsing) {
+            PendingDownloadBundle pdb = new PendingDownloadBundle();
+            pdb.url = url;
+            pdb.userAgent = userAgent;
+            pdb.contentDisposition = contentDisposition;
+            pdb.mimetype = mimetype;
+            pdb.referer = referer;
+            pdb.privateBrowsing = privateBrowsing;
+            return pdb;
+        }
+    }
+
+    /**
+     * Check if there is any pending download and start the download automatically in case
+     * there is one.
+     * @param activity Activity requesting the download.
+     */
+    public static void checkPendingDownloads(Activity activity) {
+        if (pendingDownloadBundle != null) {
+            onDownloadStartNoStream(activity,  pendingDownloadBundle.url,
+                    pendingDownloadBundle.userAgent, pendingDownloadBundle.contentDisposition,
+                    pendingDownloadBundle.mimetype, pendingDownloadBundle.referer,
+                    pendingDownloadBundle.privateBrowsing);
+            pendingDownloadBundle = null;
+        }
+    }
+
     /**
      * Notify the host application a download should be done, or that
      * the data should be streamed if a streaming viewer is available.
@@ -130,7 +172,7 @@
 
     /**
      * Notify the host application a download should be done, even if there
-     * is a streaming viewer available for thise type.
+     * is a streaming viewer available for this type.
      * @param activity Activity requesting the download.
      * @param url The full url to the content that should be downloaded
      * @param userAgent User agent of the downloading application.
@@ -143,6 +185,26 @@
             String url, String userAgent, String contentDisposition,
             String mimetype, String referer, boolean privateBrowsing) {
 
+        // Check permissions first when download will be start.
+        int permissionCheck = ContextCompat.checkSelfPermission(activity,
+                android.Manifest.permission.WRITE_EXTERNAL_STORAGE);
+        if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
+            onDownloadNoStreamImpl(activity, url, userAgent, contentDisposition,
+                    mimetype, referer, privateBrowsing);
+        } else {
+            pendingDownloadBundle = PendingDownloadBundle.create(url, userAgent,
+                    contentDisposition, mimetype, referer, privateBrowsing);
+            // Permission not granted, request it from the user
+            ActivityCompat.requestPermissions(activity,
+                    new String[] {android.Manifest.permission.WRITE_EXTERNAL_STORAGE},
+                    BrowserActivity.PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
+        }
+    }
+
+    private static void onDownloadNoStreamImpl(Activity activity,
+            String url, String userAgent, String contentDisposition,
+            String mimetype, String referer, boolean privateBrowsing) {
+
         String filename = URLUtil.guessFileName(url,
                 contentDisposition, mimetype);
 
diff --git a/src/com/android/browser/PermissionsActivity.java b/src/com/android/browser/PermissionsActivity.java
new file mode 100644
index 0000000..68f2b19
--- /dev/null
+++ b/src/com/android/browser/PermissionsActivity.java
@@ -0,0 +1,143 @@
+package com.android.browser;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.AlertDialog;
+import android.app.KeyguardManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.pm.PackageManager;
+import android.preference.PreferenceManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Window;
+import android.view.WindowManager;
+import com.android.browser.R;
+
+/**
+ * Activity that shows permissions request dialogs and handles lack of critical permissions.
+ */
+public class PermissionsActivity extends Activity {
+    private static final String TAG = "PermissionsActivity";
+
+    private static String PREF_HAS_SEEN_PERMISSIONS_DIALOGS = "pref_has_seen_permissions_dialogs";
+    private static int PERMISSION_REQUEST_CODE = 1;
+    private static int RESULT_CODE_OK = 1;
+    private static int RESULT_CODE_FAILED = 2;
+
+    private int mIndexPermissionRequestStorage;
+    private boolean mShouldRequestStoragePermission;
+    private int mNumPermissionsToRequest;
+    private boolean mFlagHasStoragePermission;
+    private SharedPreferences mPrefs;
+
+    /**
+     * Close activity when secure app passes lock screen or screen turns
+     * off.
+     */
+    private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.v(TAG, "received intent, finishing: " + intent.getAction());
+            finish();
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mNumPermissionsToRequest = 0;
+        checkPermissions();
+    }
+
+    private void checkPermissions() {
+        if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
+                != PackageManager.PERMISSION_GRANTED) {
+            mNumPermissionsToRequest++;
+            mShouldRequestStoragePermission = true;
+        } else {
+            mFlagHasStoragePermission = true;
+        }
+
+        if (mNumPermissionsToRequest != 0) {
+            if (!mPrefs.getBoolean(PREF_HAS_SEEN_PERMISSIONS_DIALOGS, false)) {
+                buildPermissionsRequest();
+            } else {
+                // Permissions dialog has already been shown
+                // and we're still missing permissions.
+                handlePermissionsFailure();
+            }
+        } else {
+            handlePermissionsSuccess();
+        }
+    }
+
+    private void buildPermissionsRequest() {
+        String[] permissionsToRequest = new String[mNumPermissionsToRequest];
+        int permissionsRequestIndex = 0;
+
+        if (mShouldRequestStoragePermission) {
+            permissionsToRequest[permissionsRequestIndex] = Manifest.permission.READ_EXTERNAL_STORAGE;
+            mIndexPermissionRequestStorage = permissionsRequestIndex;
+            permissionsRequestIndex++;
+        }
+
+        Log.v(TAG, "requestPermissions count: " + permissionsToRequest.length);
+        requestPermissions(permissionsToRequest, PERMISSION_REQUEST_CODE);
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, String permissions[],
+            int[] grantResults) {
+        Log.v(TAG, "onPermissionsResult counts: " + permissions.length + ":" + grantResults.length);
+
+        if (mShouldRequestStoragePermission) {
+            if (grantResults.length > 0 && grantResults[mIndexPermissionRequestStorage] ==
+                    PackageManager.PERMISSION_GRANTED) {
+                mFlagHasStoragePermission = true;
+            } else {
+                handlePermissionsFailure();
+            }
+        }
+
+        if (mFlagHasStoragePermission) {
+            handlePermissionsSuccess();
+        }
+    }
+
+    private void handlePermissionsSuccess() {
+        Editor edit = mPrefs.edit();
+        edit.putBoolean(PREF_HAS_SEEN_PERMISSIONS_DIALOGS, true);
+        edit.commit();
+
+        Intent intent = new Intent(this, BrowserActivity.class);
+        startActivity(intent);
+        finish();
+    }
+
+    private void handlePermissionsFailure() {
+        new AlertDialog.Builder(this).setTitle(getResources().getString(R.string.browser_error_title))
+                .setMessage(getResources().getString(R.string.error_permissions))
+                .setPositiveButton(getResources().getString(R.string.dialog_dismiss),
+                        new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        finish();
+                    }
+                })
+                .show();
+    }
+}
diff --git a/src/com/android/browser/PermissionsPrompt.java b/src/com/android/browser/PermissionsPrompt.java
index 29412d9..47c6c4e 100644
--- a/src/com/android/browser/PermissionsPrompt.java
+++ b/src/com/android/browser/PermissionsPrompt.java
@@ -16,7 +16,11 @@
 
 package com.android.browser;
 
+import android.Manifest;
+import android.app.Activity;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.util.AttributeSet;
 import android.view.Gravity;
@@ -29,7 +33,9 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
+import java.util.ArrayList;
 import java.util.Enumeration;
+import java.util.List;
 import java.util.Vector;
 
 public class PermissionsPrompt extends RelativeLayout {
@@ -37,14 +43,17 @@
     private Button mAllowButton;
     private Button mDenyButton;
     private CheckBox mRemember;
+    private Context mContext;
     private PermissionRequest mRequest;
 
     public PermissionsPrompt(Context context) {
         this(context, null);
+        mContext = context;
     }
 
     public PermissionsPrompt(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mContext = context;
     }
 
     @Override
@@ -117,9 +126,33 @@
      */
     private void handleButtonClick(boolean allow) {
         hide();
-        if (allow)
-            mRequest.grant(mRequest.getResources());
-        else
+        if (allow) {
+            String[] resources = mRequest.getResources();
+            List<String> permissionsToRequest = new ArrayList<String>();
+
+            for (String resource : resources) {
+                if (resource.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE) &&
+                        mContext.checkSelfPermission(Manifest.permission.CAMERA) !=
+                        PackageManager.PERMISSION_GRANTED) {
+                    permissionsToRequest.add(Manifest.permission.CAMERA);
+                } else if (resource.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE) &&
+                        mContext.checkSelfPermission(Manifest.permission.RECORD_AUDIO) !=
+                        PackageManager.PERMISSION_GRANTED) {
+                    permissionsToRequest.add(Manifest.permission.RECORD_AUDIO);
+                }
+            }
+
+            if (permissionsToRequest.size() > 0) {
+                String[] permissions = permissionsToRequest.toArray(
+                        new String[permissionsToRequest.size()]);
+                ((Activity) mContext).requestPermissions(permissions, 1);
+                Intent intent = new Intent(mContext, PermissionsActivity.class);
+                mContext.startActivity(intent);
+            } else {
+                mRequest.grant(mRequest.getResources());
+            }
+        } else {
             mRequest.deny();
+        }
     }
 }