diff --git a/app/build.gradle b/app/build.gradle index 279bea2..614de2e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -43,7 +43,7 @@ android { abortOnError true } packagingOptions { - exclude '.README' + exclude '.readme' } } @@ -56,10 +56,10 @@ dexcount { } dependencies { - compile 'com.android.support:palette-v7:23.0.1' - compile 'com.android.support:appcompat-v7:23.0.1' - compile 'com.android.support:design:23.0.1' - compile 'com.android.support:recyclerview-v7:23.0.1' + compile 'com.android.support:palette-v7:23.1.0' + compile 'com.android.support:appcompat-v7:23.1.0' + compile 'com.android.support:design:23.1.0' + compile 'com.android.support:recyclerview-v7:23.1.0' compile 'org.jsoup:jsoup:1.8.3' compile 'com.squareup:otto:1.3.8' compile 'com.google.dagger:dagger:2.0.1' diff --git a/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java b/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java index 97bf3e1..e84ee30 100644 --- a/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/BrowserActivity.java @@ -4,7 +4,6 @@ package acr.browser.lightning.activity; -import android.animation.Animator; import android.animation.ArgbEvaluator; import android.animation.LayoutTransition; import android.animation.ValueAnimator; @@ -106,7 +105,7 @@ import acr.browser.lightning.fragment.BookmarksFragment; import acr.browser.lightning.fragment.TabsFragment; import acr.browser.lightning.object.SearchAdapter; import acr.browser.lightning.receiver.NetworkReceiver; -import acr.browser.lightning.utils.PermissionsManager; +import acr.browser.lightning.permissions.PermissionsManager; import acr.browser.lightning.utils.ProxyUtils; import acr.browser.lightning.utils.ThemeUtils; import acr.browser.lightning.utils.UrlUtils; diff --git a/app/src/main/java/acr/browser/lightning/activity/SettingsActivity.java b/app/src/main/java/acr/browser/lightning/activity/SettingsActivity.java index a61dff2..327227d 100644 --- a/app/src/main/java/acr/browser/lightning/activity/SettingsActivity.java +++ b/app/src/main/java/acr/browser/lightning/activity/SettingsActivity.java @@ -15,7 +15,7 @@ import java.util.ArrayList; import java.util.List; import acr.browser.lightning.R; -import acr.browser.lightning.utils.PermissionsManager; +import acr.browser.lightning.permissions.PermissionsManager; public class SettingsActivity extends ThemableSettingsActivity { diff --git a/app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java b/app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java index b63c4dd..2474ca4 100644 --- a/app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java +++ b/app/src/main/java/acr/browser/lightning/download/LightningDownloadListener.java @@ -13,7 +13,8 @@ import android.webkit.URLUtil; import acr.browser.lightning.R; import acr.browser.lightning.constant.Constants; -import acr.browser.lightning.utils.PermissionsManager; +import acr.browser.lightning.permissions.PermissionsManager; +import acr.browser.lightning.permissions.PermissionsResultAction; public class LightningDownloadListener implements DownloadListener { @@ -28,7 +29,7 @@ public class LightningDownloadListener implements DownloadListener { final String contentDisposition, final String mimetype, long contentLength) { PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(mActivity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, - new PermissionsManager.PermissionResult() { + new PermissionsResultAction() { @Override public void onGranted() { String fileName = URLUtil.guessFileName(url, contentDisposition, mimetype); diff --git a/app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java b/app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java index 26e5d53..619295c 100644 --- a/app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java +++ b/app/src/main/java/acr/browser/lightning/fragment/BookmarkSettingsFragment.java @@ -27,7 +27,8 @@ import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.database.BookmarkLocalSync; import acr.browser.lightning.database.BookmarkManager; import acr.browser.lightning.database.HistoryItem; -import acr.browser.lightning.utils.PermissionsManager; +import acr.browser.lightning.permissions.PermissionsManager; +import acr.browser.lightning.permissions.PermissionsResultAction; import acr.browser.lightning.utils.Utils; public class BookmarkSettingsFragment extends PreferenceFragment implements Preference.OnPreferenceClickListener { @@ -138,7 +139,7 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref switch (preference.getKey()) { case SETTINGS_EXPORT: PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(getActivity(), REQUIRED_PERMISSIONS, - new PermissionsManager.PermissionResult() { + new PermissionsResultAction() { @Override public void onGranted() { mBookmarkManager.exportBookmarks(getActivity()); @@ -152,7 +153,7 @@ public class BookmarkSettingsFragment extends PreferenceFragment implements Pref return true; case SETTINGS_IMPORT: PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(getActivity(), REQUIRED_PERMISSIONS, - new PermissionsManager.PermissionResult() { + new PermissionsResultAction() { @Override public void onGranted() { loadFileList(null); diff --git a/app/src/main/java/acr/browser/lightning/permissions/PermissionsManager.java b/app/src/main/java/acr/browser/lightning/permissions/PermissionsManager.java new file mode 100644 index 0000000..5fbc533 --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/permissions/PermissionsManager.java @@ -0,0 +1,176 @@ +package acr.browser.lightning.permissions; + +import android.app.Activity; +import android.app.Fragment; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.ActivityCompat; +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * A class to help you manage your permissions simply. + */ +public final class PermissionsManager { + + private static final String TAG = PermissionsManager.class.getSimpleName(); + private static final String[] EMPTY_PERMISSIONS = new String[0]; + private static final PermissionsManager INSTANCE = new PermissionsManager(); + + private final Set mPendingRequests = new HashSet<>(1); + private final List mPendingActions = new ArrayList<>(1); + + public static PermissionsManager getInstance() { + return INSTANCE; + } + + private PermissionsManager() {} + + /** + * This method adds the {@link PermissionsResultAction} to the current list + * of pending actions that will be completed when the permissions are + * received. The list of permissions passed to this method are registered + * in the PermissionsResultAction object so that it will be notified of changes + * made to these permissions. + * + * @param permissions the required permissions for the action to be executed + * @param action the action to add to the current list of pending actions + */ + private synchronized void addPendingAction(@NonNull String[] permissions, @Nullable PermissionsResultAction action) { + if (action == null) { + return; + } + action.registerPermissions(permissions); + mPendingActions.add(action); + } + + /** + * This method will request all the permissions declared in your application manifest + * for the specified {@link PermissionsResultAction}. The purpose of this method is to enable + * all permissions to be requested at one shot. The PermissionsResultAction is used to notify + * you of the user allowing or denying each permission. The Activity and PermissionsResultAction + * parameters are both annotated Nullable, but this method will not work if the Activity + * is null. It is only annotated Nullable as a courtesy to prevent crashes in the case + * that you call this from a Fragment where {@link Fragment#getActivity()} could yield + * null. Additionally, you will not receive any notification of permissions being granted + * if you provide a null PermissionsResultAction. + * + * @param activity the Activity necessary to request and check permissions. + * @param action the PermissionsResultAction used to notify you of permissions being accepted. + */ + public synchronized void requestAllManifestPermissionsIfNecessary(final @Nullable Activity activity, + final @Nullable PermissionsResultAction action) { + if (activity == null) { + return; + } + Handler handler = new Handler(Looper.getMainLooper()); + handler.post(new Runnable() { + @Override + public void run() { + PackageInfo packageInfo = null; + try { + Log.d(TAG, activity.getPackageName()); + packageInfo = activity.getPackageManager().getPackageInfo(activity.getPackageName(), PackageManager.GET_PERMISSIONS); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "A problem occurred when retrieving permissions", e); + } + if (packageInfo != null) { + String[] permissions = packageInfo.requestedPermissions; + if (permissions != null) { + for (String perm : permissions) { + Log.d(TAG, "Requesting permission if necessary: " + perm); + } + } else { + permissions = EMPTY_PERMISSIONS; + } + requestPermissionsIfNecessaryForResult(activity, permissions, action); + } + } + }); + } + + /** + * This method should be used to execute a {@link PermissionsResultAction} for the array + * of permissions passed to this method. This method will request the permissions if + * they need to be requested (i.e. we don't have permission yet) and will add the + * PermissionsResultAction to the queue to be notified of permissions being granted or + * denied. In the case of pre-Android Marshmallow, permissions will be granted immediately. + * The Activity variable is nullable, but if it is null, the method will fail to execute. + * This is only nullable as a courtesy for Fragments where getActivity() may yeild null + * if the Fragment is not currently added to its parent Activity. + * + * @param activity the activity necessary to request the permissions + * @param permissions the list of permissions to request for the {@link PermissionsResultAction} + * @param action the PermissionsResultAction to notify when the permissions are granted or denied + */ + public synchronized void requestPermissionsIfNecessaryForResult(@Nullable Activity activity, + @NonNull String[] permissions, + @Nullable PermissionsResultAction action) { + if (activity == null) { + return; + } + addPendingAction(permissions, action); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + for (String perm : permissions) { + if (action != null) { + if (ActivityCompat.checkSelfPermission(activity, perm) != PackageManager.PERMISSION_GRANTED) { + action.onResult(perm, PackageManager.PERMISSION_DENIED); + } else { + action.onResult(perm, PackageManager.PERMISSION_GRANTED); + } + } + } + } else { + List permList = new ArrayList<>(1); + for (String perm : permissions) { + if (ActivityCompat.checkSelfPermission(activity, perm) != PackageManager.PERMISSION_GRANTED) { + if (!mPendingRequests.contains(perm)) { + permList.add(perm); + } + } else { + if (action != null) { + action.onResult(perm, PackageManager.PERMISSION_GRANTED); + } + } + } + if (!permList.isEmpty()) { + String[] permsToRequest = permList.toArray(new String[permList.size()]); + mPendingRequests.addAll(permList); + ActivityCompat.requestPermissions(activity, permsToRequest, 1); + } + } + } + + /** + * This method notifies the PermissionsManager that the permissions have change. It should + * be called from the Activity callback onRequestPermissionsResult() with the variables + * passed to that method. It will notify all the pending PermissionsResultAction objects currently + * in the queue, and will remove the permissions request from the list of pending requests. + * + * @param permissions the permissions that have changed + * @param results the values for each permission + */ + public synchronized void notifyPermissionsChange(@NonNull String[] permissions, @NonNull int[] results) { + int size = permissions.length; + if (results.length < size) { + size = results.length; + } + for (int n = 0; n < size; n++) { + for (PermissionsResultAction result : mPendingActions) { + result.onResult(permissions[n], results[n]); + mPendingRequests.remove(permissions[n]); + } + } + } + + +} diff --git a/app/src/main/java/acr/browser/lightning/permissions/PermissionsResultAction.java b/app/src/main/java/acr/browser/lightning/permissions/PermissionsResultAction.java new file mode 100644 index 0000000..ccff97d --- /dev/null +++ b/app/src/main/java/acr/browser/lightning/permissions/PermissionsResultAction.java @@ -0,0 +1,57 @@ +package acr.browser.lightning.permissions; + +import android.content.pm.PackageManager; +import android.support.annotation.NonNull; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * This abstract class should be used to create an if/else action that the PermissionsManager + * can execute when the permissions you request are granted or denied. Simple use involves + * creating an anonymous instance of it and passing that instance to the + * requestPermissionsIfNecessaryForResult method. The result will be sent back to you as + * either onGranted (all permissions have been granted), or onDenied (a required permission + * has been denied). Ideally you put your functionality in the onGranted method and notify + * the user what won't work in the onDenied method. + */ +public abstract class PermissionsResultAction { + + private final Set mPermissions = new HashSet<>(1); + + public abstract void onGranted(); + + public abstract void onDenied(String permission); + + /** + * This method is called when a particular permission has changed. + * This method will be called for all permissions, so this method determines + * if the permission affects the state or not and whether it can proceed with + * calling onGranted or if onDenied should be called. + * + * @param permission the permission that changed + * @param result the result for that permission + */ + public synchronized final void onResult(String permission, int result) { + if (result == PackageManager.PERMISSION_GRANTED) { + mPermissions.remove(permission); + if (mPermissions.isEmpty()) { + onGranted(); + } + } else { + onDenied(permission); + } + } + + /** + * This method registers the PermissionsResultAction object for the specified permissions + * so that it will know which permissions to look for changes to. The PermissionsResultAction + * will then know to look out for changes to these permissions. + * + * @param perms the permissions to listen for + */ + public synchronized final void registerPermissions(@NonNull String[] perms) { + Collections.addAll(mPermissions, perms); + } +} diff --git a/app/src/main/java/acr/browser/lightning/utils/PermissionsManager.java b/app/src/main/java/acr/browser/lightning/utils/PermissionsManager.java deleted file mode 100644 index f8a2c91..0000000 --- a/app/src/main/java/acr/browser/lightning/utils/PermissionsManager.java +++ /dev/null @@ -1,167 +0,0 @@ -package acr.browser.lightning.utils; - -import android.app.Activity; -import android.content.pm.PackageManager; -import android.os.Build; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.ActivityCompat; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * A class to help you manage your permissions - */ -public final class PermissionsManager { - - private final static PermissionsManager INSTANCE = new PermissionsManager(); - private final Set mPendingRequests = new HashSet<>(1); - private final List mPendingActions = new ArrayList<>(1); - - public static PermissionsManager getInstance() { - return INSTANCE; - } - - /** - * This method adds the {@link PermissionResult} to the current list - * of pending actions that will be completed when the permissions are - * received. The list of permissions passed to this method are registered - * in the PermissionResult object so that it will be notified of changes - * made to these permissions. - * - * @param permissions the required permissions for the result to be executed - * @param result the result to add to the current list of pending actions - */ - private synchronized void addPendingAction(@NonNull String[] permissions, @Nullable PermissionResult result) { - if (result == null) { - return; - } - result.registerPermissions(permissions); - mPendingActions.add(result); - } - - /** - * This method should be used to execute a {@link PermissionResult} for the array - * of permissions passed to this method. This method will request the permissions if - * they need to be requested (i.e. we don't have permission yet) and will add the - * PermissionResult to the queue to be notified of permissions being granted or - * denied. In the case of pre-Android Marshmallow, permissions will be granted immediately. - * The Activity variable is nullable, but if it is null, the method will fail to execute. - * This is only nullable as a courtesy for Fragments where getActivity() may yeild null - * if the Fragment is not currently added to its parent Activity. - * - * @param activity the activity necessary to request the permissions - * @param permissions the list of permissions to request for the {@link PermissionResult} - * @param result the PermissionResult to notify when the permissions are granted or denied - */ - public synchronized void requestPermissionsIfNecessaryForResult(@Nullable Activity activity, - @NonNull String[] permissions, - @Nullable PermissionResult result) { - if (activity == null) { - return; - } - addPendingAction(permissions, result); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - for (String perm : permissions) { - if (result != null) { - result.onResult(perm, PackageManager.PERMISSION_GRANTED); - } - } - return; - } - List permList = new ArrayList<>(1); - for (String perm : permissions) { - if (ActivityCompat.checkSelfPermission(activity, perm) != PackageManager.PERMISSION_GRANTED) { - if (!mPendingRequests.contains(perm)) { - permList.add(perm); - } - } else { - if (result != null) { - result.onResult(perm, PackageManager.PERMISSION_GRANTED); - } - } - } - if (!permList.isEmpty()) { - String[] permsToRequest = permList.toArray(new String[permList.size()]); - mPendingRequests.addAll(permList); - ActivityCompat.requestPermissions(activity, permsToRequest, 1); - } - - } - - /** - * This method notifies the PermissionsManager that the permissions have change. It should - * be called from the Activity callback onRequestPermissionsResult() with the variables - * passed to that method. It will notify all the pending PermissionResult objects currently - * in the queue, and will remove the permissions request from the list of pending requests. - * - * @param permissions the permissions that have changed - * @param results the values for each permission - */ - public synchronized void notifyPermissionsChange(@NonNull String[] permissions, @NonNull int[] results) { - int size = permissions.length; - if (results.length < size) { - size = results.length; - } - for (int n = 0; n < size; n++) { - for (PermissionResult result : mPendingActions) { - result.onResult(permissions[n], results[n]); - mPendingRequests.remove(permissions[n]); - } - } - } - - /** - * This abstract class should be used to create an if/else action that the PermissionsManager - * can execute when the permissions you request are granted or denied. Simple use involves - * creating an anonymous instance of it and passing that instance to the - * requestPermissionsIfNecessaryForResult method. The result will be sent back to you as - * either onGranted (all permissions have been granted), or onDenied (a required permission - * has been denied). Ideally you put your functionality in the onGranted method and notify - * the user what won't work in the onDenied method. - */ - public static abstract class PermissionResult { - - private final Set mPermissions = new HashSet<>(1); - - public abstract void onGranted(); - - public abstract void onDenied(String permission); - - /** - * This method is called when a particular permission has changed. - * This method will be called for all permissions, so this method determines - * if the permission affects the state or not and whether it can proceed with - * calling onGranted or if onDenied should be called. - * - * @param permission the permission that changed - * @param result the result for that permission - */ - public synchronized final void onResult(String permission, int result) { - if (result == PackageManager.PERMISSION_GRANTED) { - mPermissions.remove(permission); - if (mPermissions.isEmpty()) { - onGranted(); - } - } else { - onDenied(permission); - } - } - - /** - * This method registers the PermissionResult object for the specified permissions - * so that it will know which permissions to look for changes to. The PermissionResult - * will then know to look out for changes to these permissions. - * - * @param perms the permissions to listen for - */ - public synchronized final void registerPermissions(@NonNull String[] perms) { - Collections.addAll(mPermissions, perms); - } - } - -} diff --git a/app/src/main/java/acr/browser/lightning/utils/Utils.java b/app/src/main/java/acr/browser/lightning/utils/Utils.java index 282204c..2a7c73b 100644 --- a/app/src/main/java/acr/browser/lightning/utils/Utils.java +++ b/app/src/main/java/acr/browser/lightning/utils/Utils.java @@ -40,13 +40,15 @@ import java.util.Date; import acr.browser.lightning.R; import acr.browser.lightning.constant.Constants; import acr.browser.lightning.download.DownloadHandler; +import acr.browser.lightning.permissions.PermissionsManager; +import acr.browser.lightning.permissions.PermissionsResultAction; public final class Utils { public static void downloadFile(final Activity activity, final String url, final String userAgent, final String contentDisposition) { PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(activity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE}, new PermissionsManager.PermissionResult() { + Manifest.permission.WRITE_EXTERNAL_STORAGE}, new PermissionsResultAction() { @Override public void onGranted() { String fileName = URLUtil.guessFileName(url, null, null); diff --git a/app/src/main/java/acr/browser/lightning/view/LightningChromeClient.java b/app/src/main/java/acr/browser/lightning/view/LightningChromeClient.java index 35d234b..e97c128 100644 --- a/app/src/main/java/acr/browser/lightning/view/LightningChromeClient.java +++ b/app/src/main/java/acr/browser/lightning/view/LightningChromeClient.java @@ -28,7 +28,8 @@ import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.bus.BrowserEvents; import acr.browser.lightning.constant.Constants; import acr.browser.lightning.controller.UIController; -import acr.browser.lightning.utils.PermissionsManager; +import acr.browser.lightning.permissions.PermissionsManager; +import acr.browser.lightning.permissions.PermissionsResultAction; import acr.browser.lightning.utils.Utils; /** @@ -113,7 +114,7 @@ class LightningChromeClient extends WebChromeClient { @Override public void onGeolocationPermissionsShowPrompt(final String origin, final GeolocationPermissions.Callback callback) { - PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(mActivity, PERMISSIONS, new PermissionsManager.PermissionResult() { + PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(mActivity, PERMISSIONS, new PermissionsResultAction() { @Override public void onGranted() { final boolean remember = true;