Request ACCESS_FINE_LOCATION when necessary

P2P printing will stop working for users who configure a P2P printer
then update to Q. This pull detects this condition and posts a notification
allowing the user to fix or intentionally disable P2P. Also allows the
user to enable/disable P2P from the "Add printer" activity.

Test: Add P2P printer, deny location permissions, then start print.
Change-Id: I844b38705097cf760766fcf64d19ec6a5270e637
Merged-In: I90323c180b7de9f1d3b9fdeee3afb803f0a77a2d
Signed-off-by: Glade Diviney <mopriadevteam@gmail.com>
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index e87f721..05028e8 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -18,7 +18,7 @@
 
     <uses-feature android:name="android.hardware.wifi" android:required="false"/>
 
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
@@ -59,5 +59,6 @@
             android:exported="true"
             android:configChanges="orientation|keyboardHidden|screenSize"
             android:permission="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY" />
+
     </application>
 </manifest>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 2ff94c6..04fb35f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -65,6 +65,8 @@
     <string name="printer_not_supported">Printer not supported</string>
     <string name="wifi_direct">Wi-Fi Direct</string>
     <string name="find_wifi_direct">Find Wi-Fi Direct printers</string>
+    <!-- Preference name for enable/disable of Wi-Fi Direct printing [CHAR LIMIT=UNLIMITED] -->
+    <string name="wifi_direct_printing">Wi-Fi Direct printing</string>
     <string name="wifi_direct_printers">Wi-Fi Direct printers</string>
     <string name="searching">Searching\u2026</string>
     <string name="connect_hint_text">You may need to approve this connection on your printer\'s
@@ -95,4 +97,11 @@
     <!-- Button label in a notification. This button rejects a printer security change [CHAR LIMIT=20] -->
     <string name="reject">Reject</string>
 
+    <!-- Channel name for connection-related notifications [CHAR LIMIT=40] -->
+    <string name="connections">Connections</string>
+    <!-- Message shown in dialog, toast, or notification if the service cannot get Wi-Fi Direct permissions [CHAR LIMIT=UNLIMITED] -->
+    <string name="wifi_direct_permission_rationale">Default Print Service needs location permissions
+        to access Wi-Fi Direct printers because your location can be inferred from nearby devices.</string>
+    <!-- Button label in a notification or dialog. This button leads to a request to grant permissions [CHAR LIMIT=20] -->
+    <string name="fix">Fix</string>
 </resources>
diff --git a/res/xml/add_printers_prefs.xml b/res/xml/add_printers_prefs.xml
index ce44052..2f6d981 100644
--- a/res/xml/add_printers_prefs.xml
+++ b/res/xml/add_printers_prefs.xml
@@ -25,17 +25,24 @@
         android:persistent="false"
         android:order="1" />
 
+    <SwitchPreference
+        android:key="wifi_direct_printing"
+        android:title="@string/wifi_direct_printing"
+        android:iconSpaceReserved="true"
+        android:persistent="false"
+        android:order="2" />
+
     <Preference
         android:key="find_wifi_direct"
         android:title="@string/find_wifi_direct"
         android:icon="@drawable/ic_menu_search"
         android:persistent="false"
-        android:order="2" />
+        android:order="3" />
 
     <PreferenceCategory
         android:key="saved_printers"
         android:title="@string/saved_printers"
         android:persistent="false"
-        android:order="3" />
+        android:order="4" />
 
 </PreferenceScreen>
diff --git a/src/com/android/bips/BuiltInPrintService.java b/src/com/android/bips/BuiltInPrintService.java
index 9519962..f9931a5 100644
--- a/src/com/android/bips/BuiltInPrintService.java
+++ b/src/com/android/bips/BuiltInPrintService.java
@@ -66,10 +66,13 @@
             BuiltInPrintService.class.getCanonicalName() + ".CERTIFICATE_ACCEPT";
     private static final String ACTION_CERTIFICATE_REJECT =
             BuiltInPrintService.class.getCanonicalName() + ".CERTIFICATE_REJECT";
+    public static final String ACTION_P2P_PERMISSION_CANCEL =
+            BuiltInPrintService.class.getCanonicalName() + ".P2P_PERMISSION_CANCEL";
     private static final String EXTRA_CERTIFICATE = "certificate";
     private static final String EXTRA_PRINTER_ID = "printer-id";
     private static final String EXTRA_PRINTER_UUID = "printer-uuid";
     private static final int CERTIFICATE_REQUEST_ID = 1000;
+    public static final int P2P_PERMISSION_REQUEST_ID = 1001;
 
     // Present because local activities can bind, but cannot access this object directly
     private static WeakReference<BuiltInPrintService> sInstance;
@@ -86,6 +89,7 @@
     private WifiManager.WifiLock mWifiLock;
     private P2pMonitor mP2pMonitor;
     private NsdResolveQueue mNsdResolveQueue;
+    private P2pPermissionManager mP2pPermissionManager;
 
     /**
      * Return the current print service instance, if running
@@ -106,6 +110,8 @@
         }
         super.onCreate();
         createNotificationChannel();
+        mP2pPermissionManager = new P2pPermissionManager(this);
+        mP2pPermissionManager.reset();
 
         sInstance = new WeakReference<>(this);
         mBackend = new Backend(this);
@@ -138,6 +144,7 @@
     @Override
     public void onDestroy() {
         if (DEBUG) Log.d(TAG, "onDestroy()");
+        mP2pPermissionManager.closeNotification();
         mCapabilitiesCache.close();
         mP2pMonitor.stopAll();
         mBackend.close();
@@ -208,6 +215,13 @@
     }
 
     /**
+     * Return a general {@link P2pPermissionManager}
+     */
+    public P2pPermissionManager getP2pPermissionManager() {
+        return mP2pPermissionManager;
+    }
+
+    /**
      * Listen for a set of broadcast messages until stopped
      */
     public BroadcastMonitor receiveBroadcasts(BroadcastReceiver receiver, String... actions) {
@@ -341,6 +355,8 @@
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
         if (DEBUG) Log.d(TAG, "Received action=" + intent.getAction());
+        NotificationManager manager = (NotificationManager) getSystemService(
+                Context.NOTIFICATION_SERVICE);
         if (ACTION_CERTIFICATE_ACCEPT.equals(intent.getAction())) {
             byte[] certificate = intent.getByteArrayExtra(EXTRA_CERTIFICATE);
             PrinterId printerId = intent.getParcelableExtra(EXTRA_PRINTER_ID);
@@ -352,14 +368,16 @@
             }
             // Restart the job with the updated certificate in place
             mJobQueue.restart(printerId);
+            manager.cancel(TAG_CERTIFICATE_REQUEST, CERTIFICATE_REQUEST_ID);
         } else if (ACTION_CERTIFICATE_REJECT.equals(intent.getAction())) {
             // Cancel any job in certificate state for this uuid
             PrinterId printerId = intent.getParcelableExtra(EXTRA_PRINTER_ID);
             mJobQueue.cancel(printerId);
+            manager.cancel(TAG_CERTIFICATE_REQUEST, CERTIFICATE_REQUEST_ID);
+        } else if (ACTION_P2P_PERMISSION_CANCEL.equals(intent.getAction())) {
+            // Inform p2pPermissionManager the user canceled the notification (non-permanent)
+            mP2pPermissionManager.applyPermissionChange(false);
         }
-        NotificationManager manager = (NotificationManager) getSystemService(
-                Context.NOTIFICATION_SERVICE);
-        manager.cancel(TAG_CERTIFICATE_REQUEST, CERTIFICATE_REQUEST_ID);
         return START_NOT_STICKY;
     }
 }
diff --git a/src/com/android/bips/P2pPermissionManager.java b/src/com/android/bips/P2pPermissionManager.java
new file mode 100644
index 0000000..4773818
--- /dev/null
+++ b/src/com/android/bips/P2pPermissionManager.java
@@ -0,0 +1,303 @@
+/*
+ * 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.view.ContextThemeWrapper;
+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(new ContextThemeWrapper(activity,
+                android.R.style.Theme_Material_Settings))
+                .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();
+    }
+}
diff --git a/src/com/android/bips/discovery/P2pDiscovery.java b/src/com/android/bips/discovery/P2pDiscovery.java
index 4b6776a..083e88d 100644
--- a/src/com/android/bips/discovery/P2pDiscovery.java
+++ b/src/com/android/bips/discovery/P2pDiscovery.java
@@ -21,6 +21,7 @@
 import android.util.Log;
 
 import com.android.bips.BuiltInPrintService;
+import com.android.bips.P2pPermissionManager;
 import com.android.bips.p2p.P2pPeerListener;
 
 /**
@@ -32,6 +33,7 @@
     private static final boolean DEBUG = false;
 
     private boolean mDiscoveringPeers = false;
+    private P2pPermissionManager.P2pPermissionRequest mP2pPermissionRequest;
 
     public P2pDiscovery(BuiltInPrintService printService) {
         super(printService);
@@ -61,6 +63,10 @@
     @Override
     void onStop() {
         if (DEBUG) Log.d(TAG, "onStop()");
+        if (mP2pPermissionRequest != null) {
+            mP2pPermissionRequest.close();
+            mP2pPermissionRequest = null;
+        }
         if (mDiscoveringPeers) {
             mDiscoveringPeers = false;
             getPrintService().getP2pMonitor().stopDiscover(this);
@@ -73,8 +79,16 @@
         if (mDiscoveringPeers || getSavedPrinters().isEmpty()) {
             return;
         }
-        mDiscoveringPeers = true;
-        getPrintService().getP2pMonitor().discover(this);
+
+        // Only begin discovery if the user has granted permissions
+        P2pPermissionManager permissionManager = getPrintService().getP2pPermissionManager();
+        mP2pPermissionRequest = permissionManager.request(true, approved -> {
+            if (approved) {
+                mDiscoveringPeers = true;
+                getPrintService().getP2pMonitor().discover(this);
+            }
+            mP2pPermissionRequest = null;
+        });
     }
 
     @Override
diff --git a/src/com/android/bips/ui/AddPrintersActivity.java b/src/com/android/bips/ui/AddPrintersActivity.java
index 78de1dc..182b108 100644
--- a/src/com/android/bips/ui/AddPrintersActivity.java
+++ b/src/com/android/bips/ui/AddPrintersActivity.java
@@ -19,10 +19,11 @@
 
 import android.app.ActionBar;
 import android.app.Activity;
-import android.app.Fragment;
 import android.os.Bundle;
 import android.view.MenuItem;
 
+import com.android.bips.P2pPermissionManager;
+
 /**
  * Launched by system in response to an Add Printer request
  */
@@ -53,14 +54,11 @@
 
     @Override
     public void onRequestPermissionsResult(int requestCode, String[] permissions,
-            int[] grantResults) {
-        Fragment fragment = getFragmentManager().findFragmentById(android.R.id.content);
-        if (fragment != null && fragment instanceof OnPermissionChangeListener) {
-            ((OnPermissionChangeListener) fragment).onPermissionChange();
+                                           int[] grantResults) {
+        // Update permission status on any change requested by a fragment.
+        if (requestCode == P2pPermissionManager.REQUEST_P2P_PERMISSION_CODE) {
+            new P2pPermissionManager(this).applyPermissionChange(true);
         }
-    }
-
-    interface OnPermissionChangeListener {
-        void onPermissionChange();
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
     }
 }
diff --git a/src/com/android/bips/ui/AddPrintersFragment.java b/src/com/android/bips/ui/AddPrintersFragment.java
index 7b51447..e122cad 100644
--- a/src/com/android/bips/ui/AddPrintersFragment.java
+++ b/src/com/android/bips/ui/AddPrintersFragment.java
@@ -26,9 +26,11 @@
 import android.preference.Preference;
 import android.preference.PreferenceCategory;
 import android.preference.PreferenceFragment;
+import android.preference.SwitchPreference;
 import android.util.Log;
 
 import com.android.bips.BuiltInPrintService;
+import com.android.bips.P2pPermissionManager;
 import com.android.bips.R;
 import com.android.bips.discovery.DiscoveredPrinter;
 import com.android.bips.p2p.P2pUtils;
@@ -42,12 +44,18 @@
 
     private static final String KEY_ADD_BY_IP = "add_by_ip";
     private static final String KEY_FIND_WIFI_DIRECT = "find_wifi_direct";
+    private static final String KEY_WIFI_DIRECT_PRINTING = "wifi_direct_printing";
     private static final String KEY_SAVED_PRINTERS = "saved_printers";
+    public static final String EXTRA_FIX_P2P_PERMISSION = "fix_p2p_permission";
+
     private static final int ORDER_SAVED = 2;
 
     private PreferenceCategory mSavedPrintersCategory;
     private Preference mAddPrinterByIpPreference;
+    private Preference mFindP2pPrintersPreference;
+    private SwitchPreference mP2pEnablePreference;
     private BuiltInPrintService mPrintService;
+    private P2pPermissionManager mP2pPermissionManager;
 
     @Override
     public void onCreate(Bundle in) {
@@ -56,16 +64,17 @@
         addPreferencesFromResource(R.xml.add_printers_prefs);
         mAddPrinterByIpPreference = getPreferenceScreen().findPreference(KEY_ADD_BY_IP);
 
-        Preference findP2pPrintersPreference = getPreferenceScreen().findPreference(
+        mFindP2pPrintersPreference = getPreferenceScreen().findPreference(
                 KEY_FIND_WIFI_DIRECT);
-        findP2pPrintersPreference.setOnPreferenceClickListener(preference -> {
+        mFindP2pPrintersPreference.setOnPreferenceClickListener(preference -> {
             getFragmentManager().beginTransaction()
                     .replace(android.R.id.content, new FindP2pPrintersFragment())
                     .addToBackStack(null)
                     .commit();
             return true;
         });
-
+        mP2pEnablePreference = (SwitchPreference) getPreferenceScreen()
+                .findPreference(KEY_WIFI_DIRECT_PRINTING);
         mSavedPrintersCategory = (PreferenceCategory) getPreferenceScreen()
                 .findPreference(KEY_SAVED_PRINTERS);
     }
@@ -78,6 +87,37 @@
         getActivity().setTitle(R.string.title_activity_add_printer);
         getContext().bindService(new Intent(getContext(), BuiltInPrintService.class), this,
                 Context.BIND_AUTO_CREATE);
+
+        mP2pPermissionManager = new P2pPermissionManager(getActivity());
+        updateP2pPreferences();
+
+        if (getActivity().getIntent().getBooleanExtra(EXTRA_FIX_P2P_PERMISSION, false)) {
+            // Additional explanation is redundant, since the user saw it in notification.
+            mP2pPermissionManager.request(false, approve -> {
+                updateP2pPreferences();
+                if (!approve) {
+                    // The user is choosing to disable by denying Location.
+                    mP2pPermissionManager.setState(P2pPermissionManager.State.DISABLED);
+                }
+            });
+        }
+
+        mP2pEnablePreference.setOnPreferenceClickListener(preference -> {
+            if (mP2pEnablePreference.isChecked()) {
+                mP2pEnablePreference.setChecked(false);
+                if (mP2pPermissionManager.getState() == P2pPermissionManager.State.DISABLED) {
+                    // We're no longer disabled, just denied
+                    mP2pPermissionManager.setState(P2pPermissionManager.State.DENIED);
+                }
+                mP2pPermissionManager.reset();
+                mP2pPermissionManager.request(true, approve -> updateP2pPreferences());
+            } else {
+                // User disabled P2P
+                mP2pPermissionManager.setState(P2pPermissionManager.State.DISABLED);
+                updateP2pPreferences();
+            }
+            return true;
+        });
     }
 
     @Override
@@ -104,13 +144,31 @@
         updateSavedPrinters();
     }
 
+    private void updateP2pPreferences() {
+        // Only allow the user to find new P2P printers when enabled
+        if (mP2pPermissionManager.isP2pEnabled()) {
+            mP2pEnablePreference.setChecked(true);
+            getPreferenceScreen().addPreference(mFindP2pPrintersPreference);
+            if (getActivity().getIntent().getBooleanExtra(EXTRA_FIX_P2P_PERMISSION, false)) {
+                // If we were only here to enable P2P permissions, go back to the print now.
+                getActivity().finish();
+            }
+        } else {
+            mP2pEnablePreference.setChecked(false);
+            getPreferenceScreen().removePreference(mFindP2pPrintersPreference);
+        }
+
+        updateSavedPrinters();
+    }
+
     @Override
     public void onServiceDisconnected(ComponentName componentName) {
         mPrintService = null;
     }
 
     private void updateSavedPrinters() {
-        int savedCount = mPrintService.getDiscovery().getSavedPrinters().size();
+        int savedCount = mPrintService == null ? 0 : mPrintService.getDiscovery()
+                .getSavedPrinters().size();
 
         if (savedCount == 0) {
             if (getPreferenceScreen().findPreference(mSavedPrintersCategory.getKey()) != null) {
@@ -124,7 +182,12 @@
             mSavedPrintersCategory.removeAll();
 
             // With the service enumerate all saved printers
+            boolean p2pEnabled = mP2pPermissionManager.isP2pEnabled();
             for (DiscoveredPrinter printer : mPrintService.getDiscovery().getSavedPrinters()) {
+                // Don't show P2P printers while P2P is disabled.
+                if (P2pUtils.isP2p(printer) && !p2pEnabled) {
+                    continue;
+                }
                 if (DEBUG) Log.d(TAG, "Adding saved printer " + printer);
                 PrinterPreference pref = new PrinterPreference(getContext(), mPrintService,
                         printer, false);
diff --git a/src/com/android/bips/ui/FindP2pPrintersFragment.java b/src/com/android/bips/ui/FindP2pPrintersFragment.java
index 6d8bb83..39b9495 100644
--- a/src/com/android/bips/ui/FindP2pPrintersFragment.java
+++ b/src/com/android/bips/ui/FindP2pPrintersFragment.java
@@ -16,12 +16,10 @@
 
 package com.android.bips.ui;
 
-import android.Manifest;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.net.wifi.p2p.WifiP2pDevice;
 import android.os.Bundle;
@@ -41,13 +39,11 @@
 /**
  * Present a list of previously-saved printers, and allow them to be removed
  */
-public class FindP2pPrintersFragment extends PreferenceFragment implements ServiceConnection,
-        AddPrintersActivity.OnPermissionChangeListener {
+public class FindP2pPrintersFragment extends PreferenceFragment implements ServiceConnection {
     private static final String TAG = FindP2pPrintersFragment.class.getSimpleName();
     private static final boolean DEBUG = false;
 
     private static final String KEY_AVAILABLE = "available";
-    private static final int REQUEST_PERMISSION = 1;
 
     private BuiltInPrintService mPrintService;
     private P2pListener mPeerDiscoveryListener;
@@ -90,37 +86,13 @@
             return;
         }
 
-        // If we do not yet have permissions, ask.
-        if (getContext().checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
-                != PackageManager.PERMISSION_GRANTED) {
-            getActivity().requestPermissions(
-                    new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
-                    REQUEST_PERMISSION);
-        } else {
-            startP2pDiscovery();
-        }
-    }
-
-    private void startP2pDiscovery() {
-        if (mPrintService != null && mPeerDiscoveryListener == null) {
+        if (mPeerDiscoveryListener == null) {
             mPeerDiscoveryListener = new P2pListener();
             mPrintService.getP2pMonitor().discover(mPeerDiscoveryListener);
         }
     }
 
     @Override
-    public void onPermissionChange() {
-        // P2P discovery requires dangerous ACCESS_COARSE_LOCATION
-        if (getContext().checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
-                == PackageManager.PERMISSION_GRANTED) {
-            startP2pDiscovery();
-        } else {
-            // Wind back out of this fragment
-            getActivity().onBackPressed();
-        }
-    }
-
-    @Override
     public void onServiceDisconnected(ComponentName componentName) {
         mPrintService = null;
     }