blob: d055a225ac784bf820a1b294210bda51e7085659 [file] [log] [blame]
/*
* Copyright (C) 2019 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.bips;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.drawable.Icon;
import android.util.Log;
import android.widget.Toast;
import com.android.bips.ui.AddPrintersActivity;
import com.android.bips.ui.AddPrintersFragment;
/**
* Manage Wi-Fi Direct permission requirements and state.
*/
public class P2pPermissionManager {
private static final String TAG = P2pPermissionManager.class.getCanonicalName();
private static final boolean DEBUG = false;
private static final String CHANNEL_ID_CONNECTIONS = "connections";
public static final int REQUEST_P2P_PERMISSION_CODE = 1000;
private static final String STATE_KEY = "state";
private static final P2pPermissionRequest sFinishedRequest = () -> { };
private final Context mContext;
private final SharedPreferences mPrefs;
private final NotificationManager mNotificationManager;
public P2pPermissionManager(Context context) {
mContext = context;
mPrefs = mContext.getSharedPreferences(TAG, 0);
mNotificationManager = mContext.getSystemService(NotificationManager.class);
}
/**
* Reset any temporary modes.
*/
public void reset() {
if (getState() == State.TEMPORARILY_DISABLED) {
setState(State.DENIED);
}
}
/**
* Update the current P2P permissions request state.
*/
public void setState(State state) {
if (DEBUG) Log.d(TAG, "Setting state=" + state);
mPrefs.edit().putString(STATE_KEY, state.name()).apply();
}
/**
* Return true if P2P features are enabled.
*/
public boolean isP2pEnabled() {
return getState() == State.ALLOWED;
}
/**
* The user has made a permissions-related choice.
*/
public void applyPermissionChange(boolean permanent) {
closeNotification();
if (hasP2pPermission()) {
setState(State.ALLOWED);
} else {
// Inform the user and don't try again for the rest of this session.
setState(permanent ? State.DISABLED : State.TEMPORARILY_DISABLED);
Toast.makeText(mContext, R.string.wifi_direct_permission_rationale, Toast.LENGTH_LONG)
.show();
}
}
/**
* Return true if the user has granted P2P-related permission.
*/
private boolean hasP2pPermission() {
return mContext.checkSelfPermission(ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED;
}
/**
* Request P2P permission from the user, until the user makes a selection or the returned
* {@link P2pPermissionRequest} is closed.
*
* Note: if requested on behalf of an Activity, the Activity MUST call
* {@link P2pPermissionManager#applyPermissionChange(boolean)} whenever
* {@link Activity#onRequestPermissionsResult(int, String[], int[])} is called with code
* {@link P2pPermissionManager#REQUEST_P2P_PERMISSION_CODE}.
*/
public P2pPermissionRequest request(boolean explain, P2pPermissionListener listener) {
// Check current permission level
State state = getState();
if (DEBUG) Log.d(TAG, "request() state=" + state);
if (state.isTerminal()) {
listener.onP2pPermissionComplete(state == State.ALLOWED);
// Nothing to close because no listener registered.
return sFinishedRequest;
}
SharedPreferences.OnSharedPreferenceChangeListener preferenceListener =
listenForPreferenceChanges(listener);
if (mContext instanceof Activity) {
Activity activity = (Activity) mContext;
if (explain && activity.shouldShowRequestPermissionRationale(ACCESS_FINE_LOCATION)) {
explain(activity);
} else {
request(activity);
}
} else {
showNotification();
}
return () -> {
// Allow the caller to close this request if it no longer cares about the result
closeNotification();
mPrefs.unregisterOnSharedPreferenceChangeListener(preferenceListener);
};
}
/**
* Use the activity to request permissions if possible.
*/
private void request(Activity activity) {
activity.requestPermissions(new String[]{ACCESS_FINE_LOCATION},
REQUEST_P2P_PERMISSION_CODE);
}
private void explain(Activity activity) {
// User denied, but asked us to use P2P, so explain and redirect to settings
DialogInterface.OnClickListener clickListener = (dialog, which) -> {
if (which == DialogInterface.BUTTON_POSITIVE) {
request(activity);
}
};
new AlertDialog.Builder(activity, android.R.style.Theme_DeviceDefault_Light_Dialog_Alert)
.setMessage(mContext.getString(R.string.wifi_direct_permission_rationale))
.setPositiveButton(R.string.fix, clickListener)
.show();
}
private SharedPreferences.OnSharedPreferenceChangeListener listenForPreferenceChanges(
P2pPermissionListener listener) {
SharedPreferences.OnSharedPreferenceChangeListener preferenceListener =
new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
State state = getState();
if (state.isTerminal() || state == State.DENIED) {
listener.onP2pPermissionComplete(state == State.ALLOWED);
mPrefs.unregisterOnSharedPreferenceChangeListener(this);
}
}
};
mPrefs.registerOnSharedPreferenceChangeListener(preferenceListener);
return preferenceListener;
}
/**
* Deliver a notification to the user.
*/
private void showNotification() {
// Because we are not in an activity create a notification to do the work
mNotificationManager.createNotificationChannel(new NotificationChannel(
CHANNEL_ID_CONNECTIONS, mContext.getString(R.string.connections),
NotificationManager.IMPORTANCE_HIGH));
Intent proceedIntent = new Intent(mContext, AddPrintersActivity.class);
proceedIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
proceedIntent.putExtra(AddPrintersFragment.EXTRA_FIX_P2P_PERMISSION, true);
PendingIntent proceedPendingIntent = PendingIntent.getActivity(mContext, 0,
proceedIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Action fixAction = new Notification.Action.Builder(
Icon.createWithResource(mContext, R.drawable.ic_printservice),
mContext.getString(R.string.fix), proceedPendingIntent).build();
Intent cancelIntent = new Intent(mContext, BuiltInPrintService.class)
.setAction(BuiltInPrintService.ACTION_P2P_PERMISSION_CANCEL);
PendingIntent cancelPendingIndent = PendingIntent.getService(mContext,
BuiltInPrintService.P2P_PERMISSION_REQUEST_ID, cancelIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
Notification.Action cancelAction = new Notification.Action.Builder(
Icon.createWithResource(mContext, R.drawable.ic_printservice),
mContext.getString(android.R.string.cancel), cancelPendingIndent).build();
Notification notification = new Notification.Builder(mContext, CHANNEL_ID_CONNECTIONS)
.setSmallIcon(R.drawable.ic_printservice)
.setStyle(new Notification.BigTextStyle().bigText(
mContext.getString(R.string.wifi_direct_permission_rationale)))
.setAutoCancel(true)
.setContentIntent(proceedPendingIntent)
.setDeleteIntent(cancelPendingIndent)
.addAction(fixAction)
.addAction(cancelAction)
.build();
mNotificationManager.notify(BuiltInPrintService.P2P_PERMISSION_REQUEST_ID, notification);
}
/**
* Return the current {@link State}.
*/
public State getState() {
// Look up stored state
String stateString = mPrefs.getString(STATE_KEY, State.DENIED.name());
State state = State.valueOf(stateString);
if (state == State.DISABLED) {
// If disabled do no further checking
return state;
}
boolean hasPermission = hasP2pPermission();
if (hasPermission && state != State.ALLOWED) {
// Upgrade state if now allowed
state = State.ALLOWED;
setState(state);
} else if (!hasPermission && state == State.ALLOWED) {
state = State.DENIED;
setState(state);
}
return state;
}
/**
* Close any outstanding notification.
*/
void closeNotification() {
mNotificationManager.cancel(BuiltInPrintService.P2P_PERMISSION_REQUEST_ID);
}
/**
* The current P2P permission request state.
*/
public enum State {
// The user has not granted permissions.
DENIED,
// The user did not grant permissions this time but try again next time.
TEMPORARILY_DISABLED,
// The user explicitly disabled or chose not to enable P2P.
DISABLED,
// Permissions are granted.
ALLOWED;
/** Return true if the user {@link State} is at a final permissions state. */
public boolean isTerminal() {
return this != DENIED;
}
}
/**
* Listener for determining when a P2P permission request is complete.
*/
public interface P2pPermissionListener {
/**
* Invoked when it is known that the user has allowed or denied the permission request.
*/
void onP2pPermissionComplete(boolean allowed);
}
/**
* A closeable request for grant of P2P permissions.
*/
public interface P2pPermissionRequest extends AutoCloseable {
@Override
void close();
}
}