MDM Download Restriction support

This commit adds MDM management of the following if the restriction
is enabled via the MDM mechanism:
    1. The ability to download.
    2. The download directory under '/Phone Storage/', if
       downloading is permitted.

These restrictions are only effective on SWE provisioned in the
'Work' profile. Non-work-profile SWE will not be affected.

When the download capability has been restricted, clicking a download
link results in a Toast message (Managed by your administrator). A long-press
of the link still brings up a context menu, but the 'Save link as' menuitem
is disabled in this case.

If MDM sets the download directory, it is not setting the full path, just
the directory under '/Phone Storage'. The directory specified must include
a leading '/' i.e. '/NewDownloadDir'.

Change-Id: I13a44892a36925d6324b87f7655d79fb0776f8a9
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index 04da119..6ee8e29 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -34,6 +34,7 @@
 import com.android.browser.mdm.AutoFillRestriction;
 import com.android.browser.mdm.DevToolsRestriction;
 import com.android.browser.mdm.DoNotTrackRestriction;
+import com.android.browser.mdm.DownloadDirRestriction;
 import com.android.browser.mdm.EditBookmarksRestriction;
 import com.android.browser.mdm.IncognitoRestriction;
 import com.android.browser.mdm.ManagedBookmarksRestriction;
@@ -187,6 +188,7 @@
         AutoFillRestriction.getInstance();
         DevToolsRestriction.getInstance();
         DoNotTrackRestriction.getInstance();
+        DownloadDirRestriction.getInstance();
         EditBookmarksRestriction.getInstance();
         IncognitoRestriction.getInstance();
         ManagedBookmarksRestriction.getInstance();
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index 6337237..8271475 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -96,6 +96,7 @@
 
 import com.android.browser.IntentHandler.UrlData;
 import com.android.browser.UI.ComboViews;
+import com.android.browser.mdm.DownloadDirRestriction;
 import com.android.browser.mdm.EditBookmarksRestriction;
 import com.android.browser.mdm.IncognitoRestriction;
 import com.android.browser.mdm.URLFilterRestriction;
@@ -1540,8 +1541,14 @@
             menu.setGroupVisible(R.id.ANCHOR_MENU,
                     type == WebView.HitTestResult.SRC_ANCHOR_TYPE
                             || type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE);
-            menu.findItem(R.id.save_link_context_menu_id).setEnabled(
-                UrlUtils.isDownloadableScheme(extra));
+
+            if (DownloadDirRestriction.getInstance().downloadsAllowed()) {
+                menu.findItem(R.id.save_link_context_menu_id).setEnabled(
+                        UrlUtils.isDownloadableScheme(extra));
+            }
+            else {
+                menu.findItem(R.id.save_link_context_menu_id).setEnabled(false);
+            }
         }
         // Setup custom handling depending on the type
         switch (type) {
diff --git a/src/com/android/browser/DownloadHandler.java b/src/com/android/browser/DownloadHandler.java
index 419e4b4..556b39f 100644
--- a/src/com/android/browser/DownloadHandler.java
+++ b/src/com/android/browser/DownloadHandler.java
@@ -38,6 +38,7 @@
 import android.widget.Toast;
 
 import com.android.browser.R;
+import com.android.browser.mdm.DownloadDirRestriction;
 import com.android.browser.platformsupport.WebAddress;
 import com.android.browser.reflect.ReflectHelper;
 
@@ -320,8 +321,14 @@
             new FetchUrlMimeType(activity, url, userAgent, referer,
                     privateBrowsing, filename).start();
         } else {
-            startDownloadSettings(activity, url, userAgent, contentDisposition, mimetype, referer,
-                    privateBrowsing, contentLength, filename);
+            if (DownloadDirRestriction.getInstance().downloadsAllowed()) {
+                startDownloadSettings(activity, url, userAgent, contentDisposition, mimetype, referer,
+                        privateBrowsing, contentLength, filename);
+            }
+            else {
+                Toast.makeText(activity, R.string.managed_by_your_administrator, Toast.LENGTH_SHORT)
+                .show();
+            }
         }
 
     }
@@ -599,7 +606,7 @@
             defaultStorage = getExternalStorageDirectory(context);
         }
 
-        defaultDownloadPath = defaultStorage + context.getString(R.string.download_default_path);
+        defaultDownloadPath = defaultStorage + DownloadDirRestriction.getInstance().getDownloadDirectory();
         Log.e(LOGTAG, "defaultStorage directory is : " + defaultDownloadPath);
         return defaultDownloadPath;
     }
diff --git a/src/com/android/browser/mdm/DownloadDirRestriction.java b/src/com/android/browser/mdm/DownloadDirRestriction.java
new file mode 100644
index 0000000..696b0b9
--- /dev/null
+++ b/src/com/android/browser/mdm/DownloadDirRestriction.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+package com.android.browser.mdm;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.browser.Browser;
+import com.android.browser.R;
+
+public class DownloadDirRestriction extends Restriction {
+    private final static String TAG = "DownloadDirRestriction";
+    public static final String RESTRICTION_ENABLED = "DownloadRestrictionEnabled";
+    public static final String DOWNLOADS_ALLOWED = "DownloadsAllowed";
+    public static final String DOWNLOADS_DIR = "DownloadDirectory";
+
+    private static DownloadDirRestriction sInstance;
+
+    public static final boolean defaultDownloadsAllowed = true;
+    public static String defaultDownloadDir;
+
+    private String mCurrDownloadDir;
+    private boolean mCurrDownloadsAllowed;
+
+    private DownloadDirRestriction() {
+        super(TAG);
+    }
+
+    @Override
+    protected void doCustomInit() {
+        mCurrDownloadsAllowed = defaultDownloadsAllowed;
+        Context c = Browser.getContext();
+        defaultDownloadDir = c.getString(R.string.download_default_path);
+        mCurrDownloadDir = defaultDownloadDir;
+    }
+
+    public static DownloadDirRestriction getInstance() {
+        synchronized (DownloadDirRestriction.class) {
+            if (sInstance == null) {
+                sInstance = new DownloadDirRestriction();
+            }
+        }
+        return sInstance;
+    }
+
+    @Override
+    public void enforce(Bundle restrictions) {
+        enable(restrictions.getBoolean(RESTRICTION_ENABLED, false));
+
+        if (isEnabled()) {
+            mCurrDownloadsAllowed = restrictions.getBoolean(DOWNLOADS_ALLOWED, defaultDownloadsAllowed);
+            mCurrDownloadDir = restrictions.getString(DOWNLOADS_DIR, defaultDownloadDir);
+        }
+        else {
+            doCustomInit();
+        }
+    }
+
+    public boolean downloadsAllowed() {
+        return mCurrDownloadsAllowed;
+    }
+
+    public String getDownloadDirectory() {
+        return mCurrDownloadDir;
+    }
+}
diff --git a/src/com/android/browser/mdm/tests/DownloadDirRestrictionsTest.java b/src/com/android/browser/mdm/tests/DownloadDirRestrictionsTest.java
new file mode 100644
index 0000000..743f017
--- /dev/null
+++ b/src/com/android/browser/mdm/tests/DownloadDirRestrictionsTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+package com.android.browser.mdm.tests;
+
+import android.app.Instrumentation;
+import android.os.Bundle;
+import android.test.ActivityInstrumentationTestCase2;
+
+import com.android.browser.BrowserActivity;
+import com.android.browser.mdm.DownloadDirRestriction;
+import com.android.browser.mdm.ManagedProfileManager;
+
+public class DownloadDirRestrictionsTest extends ActivityInstrumentationTestCase2<BrowserActivity> {
+
+    // private final static String TAG = "IncognitoRestrictionsTest";
+
+    private Instrumentation mInstrumentation;
+    private BrowserActivity mActivity;
+    private DownloadDirRestriction mDDirRestriction;
+
+    public DownloadDirRestrictionsTest() {
+        super(BrowserActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mInstrumentation = getInstrumentation();
+        mActivity = getActivity();
+        mDDirRestriction = DownloadDirRestriction.getInstance();
+    }
+
+    public void test_DownloadDirRestriction() throws Throwable {
+        // Possible states
+        // Enabled  Downloads  Download | allowed	dir
+        //          Allowed     Dir     |
+        // -----------------------------+-------------------
+        // not set     n       default  |    y     default
+        // not set     n       /fubar   |    y     default
+        // not set     y       default  |    y     default
+        // not set     y       /fubar   |    y     default
+        //   No        n       default  |    y     default
+        //   No        n       /fubar   |    y     default
+        //   No        y       default  |    y     default
+        //   No        y       /fubar   |    y     default
+        //   Yes       n       default  |    n     default (Dir wouldn't be noticed since the dialog won't be seen)
+        //   Yes       n       /fubar   |    n     /fubar  (Dir wouldn't be noticed since the dialog won't be seen)
+        //   Yes       y       default  |    y     default
+        //   Yes       y       /fubar   |    y     /fubar
+
+
+        // Initial conditions: no restrictions at all
+        clearAllRestrictions();
+        assertFalse(mDDirRestriction.isEnabled());
+        assertEquals(DownloadDirRestriction.defaultDownloadsAllowed, mDDirRestriction.downloadsAllowed());
+        assertEquals(DownloadDirRestriction.defaultDownloadDir, mDDirRestriction.getDownloadDirectory());
+
+        // Enable not set
+        setDDirRestrictions("NotSet", false, DownloadDirRestriction.defaultDownloadDir);
+        assertFalse(mDDirRestriction.isEnabled());
+        assertEquals(DownloadDirRestriction.defaultDownloadsAllowed, mDDirRestriction.downloadsAllowed());
+        assertEquals(DownloadDirRestriction.defaultDownloadDir, mDDirRestriction.getDownloadDirectory());
+
+        setDDirRestrictions("NotSet", false, "/fubar");
+        assertFalse(mDDirRestriction.isEnabled());
+        assertEquals(DownloadDirRestriction.defaultDownloadsAllowed, mDDirRestriction.downloadsAllowed());
+        assertEquals(DownloadDirRestriction.defaultDownloadDir, mDDirRestriction.getDownloadDirectory());
+
+        setDDirRestrictions("NotSet", true, DownloadDirRestriction.defaultDownloadDir);
+        assertFalse(mDDirRestriction.isEnabled());
+        assertEquals(DownloadDirRestriction.defaultDownloadsAllowed, mDDirRestriction.downloadsAllowed());
+        assertEquals(DownloadDirRestriction.defaultDownloadDir, mDDirRestriction.getDownloadDirectory());
+
+        setDDirRestrictions("NotSet", true, "/fubar");
+        assertFalse(mDDirRestriction.isEnabled());
+        assertEquals(DownloadDirRestriction.defaultDownloadsAllowed, mDDirRestriction.downloadsAllowed());
+        assertEquals(DownloadDirRestriction.defaultDownloadDir, mDDirRestriction.getDownloadDirectory());
+
+        // Enable is false
+        setDDirRestrictions("false", false, DownloadDirRestriction.defaultDownloadDir);
+        assertFalse(mDDirRestriction.isEnabled());
+        assertEquals(DownloadDirRestriction.defaultDownloadsAllowed, mDDirRestriction.downloadsAllowed());
+        assertEquals(DownloadDirRestriction.defaultDownloadDir, mDDirRestriction.getDownloadDirectory());
+
+        setDDirRestrictions("false", false, "/fubar");
+        assertFalse(mDDirRestriction.isEnabled());
+        assertEquals(DownloadDirRestriction.defaultDownloadsAllowed, mDDirRestriction.downloadsAllowed());
+        assertEquals(DownloadDirRestriction.defaultDownloadDir, mDDirRestriction.getDownloadDirectory());
+
+        setDDirRestrictions("false", true, DownloadDirRestriction.defaultDownloadDir);
+        assertFalse(mDDirRestriction.isEnabled());
+        assertEquals(DownloadDirRestriction.defaultDownloadsAllowed, mDDirRestriction.downloadsAllowed());
+        assertEquals(DownloadDirRestriction.defaultDownloadDir, mDDirRestriction.getDownloadDirectory());
+
+        setDDirRestrictions("false", true, "/fubar");
+        assertFalse(mDDirRestriction.isEnabled());
+        assertEquals(DownloadDirRestriction.defaultDownloadsAllowed, mDDirRestriction.downloadsAllowed());
+        assertEquals(DownloadDirRestriction.defaultDownloadDir, mDDirRestriction.getDownloadDirectory());
+
+        // Enable is True
+        setDDirRestrictions("true", false, DownloadDirRestriction.defaultDownloadDir);
+        assertTrue(mDDirRestriction.isEnabled());
+        assertFalse(mDDirRestriction.downloadsAllowed());
+        assertEquals(DownloadDirRestriction.defaultDownloadDir, mDDirRestriction.getDownloadDirectory());
+
+        setDDirRestrictions("true", false, "/fubar");
+        assertTrue(mDDirRestriction.isEnabled());
+        assertFalse(mDDirRestriction.downloadsAllowed());
+        assertEquals("/fubar", mDDirRestriction.getDownloadDirectory());
+
+        setDDirRestrictions("true", true, DownloadDirRestriction.defaultDownloadDir);
+        assertTrue(mDDirRestriction.isEnabled());
+        assertTrue(mDDirRestriction.downloadsAllowed());
+        assertEquals(DownloadDirRestriction.defaultDownloadDir, mDDirRestriction.getDownloadDirectory());
+
+        setDDirRestrictions("true", true, "/fubar");
+        assertTrue(mDDirRestriction.isEnabled());
+        assertTrue(mDDirRestriction.downloadsAllowed());
+        assertEquals("/fubar", mDDirRestriction.getDownloadDirectory());
+    }
+
+    /**
+    * Activate Download Directory restriction
+    * @param enabled  Required. "NotSet" | "true" | "false"
+    * @param allow determines if downloads are allowed
+    * @param dir override download dir
+    *
+    */
+    private void setDDirRestrictions(String enabled, boolean allow, String dir) {
+        final Bundle restrictions = new Bundle();
+
+        if (!enabled.equals("NotSet")) {
+            if (enabled.equals("true")) {
+                restrictions.putBoolean(DownloadDirRestriction.RESTRICTION_ENABLED, true);
+            } else if (enabled.equals("false")) {
+                restrictions.putBoolean(DownloadDirRestriction.RESTRICTION_ENABLED, false);
+            }
+        }
+
+        restrictions.putBoolean(DownloadDirRestriction.DOWNLOADS_ALLOWED, allow);
+        restrictions.putString(DownloadDirRestriction.DOWNLOADS_DIR, dir);
+
+        sendRestriction(restrictions);
+    }
+
+    private void clearAllRestrictions() {
+        sendRestriction(new Bundle());
+    }
+
+    private void sendRestriction(final Bundle restrictions) {
+        // Deliver restriction on UI thread
+        mActivity.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                ManagedProfileManager.getInstance().setMdmRestrictions(restrictions);
+            }
+        });
+
+        // Wait to ensure restriction is set
+        mInstrumentation.waitForIdleSync();
+    }
+}