/******************************************************************************* * Copyright 2016 Specure GmbH * * 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 at.alladin.rmbt.android.util; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.Manifest; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.pm.PackageManager; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.util.Log; import at.alladin.openrmbt.android.R; import at.alladin.rmbt.android.main.RMBTMainActivity; public abstract class PermissionHelper { private static final String TAG = "PermissionHelper"; public static final int REQUEST_AT_INIT = 1; public static final int REQUEST_AT_TEST_START = 2; protected static final String PREF_LAST_TIME_PERMISSION_SHOWN = "lastTimePermissionShown"; protected static final long SHOW_PERMISSION_DIALOG_INTERVAL_SEC = 3600; // 1h // protected static final long SHOW_PERMISSION_DIALOG_INTERVAL_SEC = 10; // TODO: REMOVE, only for debugging protected static final ConcurrentHashMap<String, Boolean> DYNAMIC_PERMISSIONS = new ConcurrentHashMap<String, Boolean>(); protected static class PermissionGroup { private final String groupName; private final String[] permissions; private final int dialogTitle; private final int dialogMessage; public PermissionGroup(String groupName, String[] permissions) { this.groupName = groupName; this.dialogTitle = -1; this.dialogMessage = -1; this.permissions = permissions; } public PermissionGroup(String groupName, int dialogTitle, int dialogMessage, String[] permissions) { this.groupName = groupName; this.dialogTitle = dialogTitle; this.dialogMessage = dialogMessage; this.permissions = permissions; } } protected static final PermissionGroup LOCATION_GROUP = new PermissionGroup("location", new String[] { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, } ); protected static final PermissionGroup TELEPHONY_GROUP = new PermissionGroup("telephony", R.string.permission_telephony_dialog_title, R.string.permission_telephony_dialog_text, new String[] { Manifest.permission.READ_PHONE_STATE } ); protected static final PermissionGroup[] GROUPS = new PermissionGroup[] { LOCATION_GROUP, TELEPHONY_GROUP }; protected static final String[] PERMISSIONS = mergePermissions(GROUPS); protected static String[] mergePermissions(PermissionGroup[] groups) { final List<String> permissions = new ArrayList<String>(); for (PermissionGroup group : groups) permissions.addAll(Arrays.asList(group.permissions)); return permissions.toArray(new String[permissions.size()]); } public static boolean checkPermission(Context ctx, String permission) { return ContextCompat.checkSelfPermission(ctx, permission) == PackageManager.PERMISSION_GRANTED; } public static boolean checkAllPermissions(Context ctx, List<String> permissions) { for (String permission : permissions) if (! checkPermission(ctx, permission)) return false; return true; } public static boolean checkAllPermissions(Context ctx, String[] permissions) { for (String permission : permissions) if (! checkPermission(ctx, permission)) return false; return true; } public static boolean checkAnyPermission(Context ctx, String[] permissions) { for (String permission : permissions) if (checkPermission(ctx, permission)) return true; return false; } public static boolean checkCoarseLocationPermission(Context ctx) { return checkPermission(ctx, Manifest.permission.ACCESS_COARSE_LOCATION); } public static boolean checkFineLocationPermission(Context ctx) { return checkPermission(ctx, Manifest.permission.ACCESS_FINE_LOCATION); } public static boolean checkReadPhoneStatePermission(Context ctx) { return checkPermission(ctx, Manifest.permission.READ_PHONE_STATE); } public static boolean checkAnyLocationPermission(Context ctx) { return checkAnyPermission(ctx, LOCATION_GROUP.permissions); } protected static boolean checkAllDynamicPermissions(Context ctx, String[] permissions) { return checkAllPermissions(ctx, filterDynamicPermissions(PERMISSIONS)); } protected static List<String> getMissingDynamicPermissions(Context ctx, String[] permissions) { final List<String> missingPermissions = new ArrayList<String>(); for (String permission : filterDynamicPermissions(permissions)) { if (! checkPermission(ctx, permission)) missingPermissions.add(permission); } return missingPermissions; } protected static List<String> filterDynamicPermissions(String[] permissions) { final List<String> result = new ArrayList<String>(); for (String permission : permissions) { final Boolean request = DYNAMIC_PERMISSIONS.get(permission); if (request == null || request) // default to true if not set result.add(permission); } return result; } protected static boolean shouldShowRequestPermissionRationale(Activity act, String[] permissions) { for (String permission : filterDynamicPermissions(permissions)) { if (ActivityCompat.shouldShowRequestPermissionRationale(act, permission)) return true; } return false; } public static void checkPermissionAtTestStartAndStartTest(final RMBTMainActivity act) { if (checkAllDynamicPermissions(act, PERMISSIONS)) // everything is fine { act.startTest(); return; } final List<PermissionGroup> neededGroups = new ArrayList<PermissionHelper.PermissionGroup>(); for (PermissionGroup group : GROUPS) { if (! checkAllDynamicPermissions(act, group.permissions) && isItTimeToShowPermissionDialogAgain(act, group.groupName)) neededGroups.add(group); } if (neededGroups.isEmpty()) // nothing to do { act.startTest(); return; } final List<String> neededPermissions = new ArrayList<String>(); handleGroup(act, neededGroups, neededPermissions, 0); } protected static void handleGroup(final RMBTMainActivity act, final List<PermissionGroup> neededGroups, final List<String> neededPermissions, final int i) { final boolean afterLastGroup = i >= neededGroups.size(); if (!afterLastGroup) { final PermissionGroup group = neededGroups.get(i); updatePermissionDialogTime(act, group.groupName); if (group.dialogTitle != -1 && group.dialogMessage != -1 && shouldShowRequestPermissionRationale(act, group.permissions)) { final AlertDialog.Builder builder = new AlertDialog.Builder(act); builder .setTitle(group.dialogTitle) .setMessage(group.dialogMessage) /* .setNegativeButton("Abbrechen", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { handleGroup(act, neededGroups, neededPermissions, i + 1); } }) */ .setPositiveButton("Weiter", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { neededPermissions.addAll(getMissingDynamicPermissions(act, group.permissions)); handleGroup(act, neededGroups, neededPermissions, i + 1); } }) .show(); } else { neededPermissions.addAll(getMissingDynamicPermissions(act, group.permissions)); handleGroup(act, neededGroups, neededPermissions, i + 1); } } else // afterLastGroup { if (neededPermissions.isEmpty()) act.startTest(); else ActivityCompat.requestPermissions(act, neededPermissions.toArray(new String[neededPermissions.size()]), REQUEST_AT_TEST_START); } } public static void checkPermissionAtInit(final Activity act) { // request location permissions if (checkAllPermissions(act, LOCATION_GROUP.permissions)) return; updatePermissionDialogTime(act, LOCATION_GROUP.groupName); ActivityCompat.requestPermissions(act, LOCATION_GROUP.permissions, REQUEST_AT_INIT); } public static boolean isItTimeToShowPermissionDialogAgain(Context ctx, String groupName) { final long lastTime = ConfigHelper.getSharedPreferences(ctx).getLong(PREF_LAST_TIME_PERMISSION_SHOWN + "_" + groupName, 0); return (lastTime <= 0 || lastTime + SHOW_PERMISSION_DIALOG_INTERVAL_SEC * 1000 < System.currentTimeMillis()); } public static void updatePermissionDialogTime(Context ctx, String groupName) { ConfigHelper.getSharedPreferences(ctx).edit().putLong(PREF_LAST_TIME_PERMISSION_SHOWN + "_" + groupName, System.currentTimeMillis()).commit(); } public static JSONArray getPermissionStatusAsJSONArray(Context ctx) { final JSONArray result = new JSONArray(); for (String permission : PERMISSIONS) { final JSONObject obj = new JSONObject(); try { obj.put("permission", permission); obj.put("status", checkPermission(ctx, permission)); result.put(obj); } catch (JSONException e) { e.printStackTrace(); } } Log.d(TAG, result.toString()); return result; } public static void setRequestPermissions(JSONArray permissions) { DYNAMIC_PERMISSIONS.clear(); for (int i = 0; i < permissions.length(); i++) { final JSONObject obj = permissions.optJSONObject(i); if (obj != null) { final String permission = obj.optString("permission"); final boolean request = obj.optBoolean("request"); if (permission != null) DYNAMIC_PERMISSIONS.put(permission, request); } } } }