Prefer printers that have been used physically close to the current
location of the user.

Commonly the users prints on a printer that this near the user current
location. Hence if possible we want to show the user printers that he
used before and that are close to him.

Hence store the location of the previous uses of a printer and prefer
printer that are close to the user.

Unfortunately getLastLocation might not report a usable location and it
will take at least 5 seconds until the first usable location arrives. At
this time the user might have already opened the destionation spinner.
It would be unexpected for the printers to suddenly change under the
users finger. Hence it might be that we did first show the printer
without any location information and then once the location is known we
cannot update thedestination spinner anymore.

The select printer activity does not have this issue, hence in the worst
case the user has to enter this activity to select a printer and by then
the location is usually determined.

This is not ideal but better than before.

Bug: 24133609
Change-Id: Ie7d20cf3d9dd163e57903f8f6ecc0b3fd4f4374e
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index 937eb15..af9d251 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -38,6 +38,8 @@
     <uses-permission android:name="com.android.printspooler.permission.ACCESS_ALL_PRINT_JOBS"/>
     <uses-permission android:name="android.permission.WAKE_LOCK"/>
     <uses-permission android:name="android.permission.START_PRINT_SERVICE_CONFIG_ACTIVITY"/>
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
 
     <application
         android:allowClearUserData="true"
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
index c3c118a..5525774 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
@@ -20,7 +20,15 @@
 import android.content.Context;
 import android.content.Loader;
 import android.content.pm.ServiceInfo;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.location.LocationRequest;
 import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.SystemClock;
 import android.print.PrintManager;
 import android.print.PrinterDiscoverySession;
 import android.print.PrinterDiscoverySession.OnPrintersChangeListener;
@@ -31,11 +39,14 @@
 import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Slog;
 import android.util.Xml;
 
 import com.android.internal.util.FastXmlSerializer;
 
+import libcore.io.IoUtils;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -48,19 +59,19 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
-import libcore.io.IoUtils;
-
 /**
  * This class is responsible for loading printers by doing discovery
  * and merging the discovered printers with the previously used ones.
  */
-public final class FusedPrintersProvider extends Loader<List<PrinterInfo>> {
+public final class FusedPrintersProvider extends Loader<List<PrinterInfo>>
+        implements LocationListener {
     private static final String LOG_TAG = "FusedPrintersProvider";
 
     private static final boolean DEBUG = false;
@@ -70,10 +81,22 @@
 
     private static final int MAX_FAVORITE_PRINTER_COUNT = 4;
 
+    /** Interval of location updated in ms */
+    private static final int LOCATION_UPDATE_MS = 30 * 1000;
+
+    /** Maximum acceptable age of the location in ms */
+    private static final int MAX_LOCATION_AGE_MS = 10 * 60 * 1000;
+
+    /** The worst accuracy that is considered usable in m */
+    private static final int MIN_LOCATION_ACCURACY = 50;
+
+    /** Maximum distance where a printer is still considered "near" */
+    private static final int MAX_PRINTER_DISTANCE = MIN_LOCATION_ACCURACY * 2;
+
     private final List<PrinterInfo> mPrinters =
             new ArrayList<>();
 
-    private final List<PrinterInfo> mFavoritePrinters =
+    private final List<Pair<PrinterInfo, Location>> mFavoritePrinters =
             new ArrayList<>();
 
     private final PersistenceManager mPersistenceManager;
@@ -84,33 +107,111 @@
 
     private boolean mPrintersUpdatedBefore;
 
+    /** Last known location, can be null or out of date */
+    private final Object mLocationLock;
+    private Location mLocation;
+
+    /** Location used when the printers were updated the last time */
+    private Location mLocationOfLastPrinterUpdate;
+
+    /** Reference to the system's location manager */
+    private final LocationManager mLocationManager;
+
+    /**
+     * Get a reference to the current location.
+     */
+    private Location getCurrentLocation() {
+        synchronized (mLocationLock) {
+            return mLocation;
+        }
+    }
+
     public FusedPrintersProvider(Context context) {
         super(context);
+        mLocationLock = new Object();
         mPersistenceManager = new PersistenceManager(context);
+        mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
     }
 
     public void addHistoricalPrinter(PrinterInfo printer) {
         mPersistenceManager.addPrinterAndWritePrinterHistory(printer);
     }
 
+    /**
+     * Add printer to dest, or if updatedPrinters add the updated printer. If the updated printer
+     * was added, remove it from updatedPrinters.
+     *
+     * @param dest The list the printers should be added to
+     * @param printer The printer to add
+     * @param updatedPrinters The printer to add
+     */
+    private void updateAndAddPrinter(List<PrinterInfo> dest, PrinterInfo printer,
+            Map<PrinterId, PrinterInfo> updatedPrinters) {
+        PrinterInfo updatedPrinter = updatedPrinters.remove(printer.getId());
+        if (updatedPrinter != null) {
+            dest.add(updatedPrinter);
+        } else {
+            dest.add(printer);
+        }
+    }
+
+    /**
+     * Compute the printers, order them appropriately and deliver the printers to the clients. We
+     * prefer printers that have been previously used (favorites) and printers that have been used
+     * previously close to the current location (near printers).
+     *
+     * @param discoveredPrinters All printers currently discovered by the print discovery session.
+     * @param favoritePrinters The ordered list of printers. The earlier in the list, the more
+     *            preferred.
+     */
     private void computeAndDeliverResult(Map<PrinterId, PrinterInfo> discoveredPrinters,
-            List<PrinterInfo> favoritePrinters) {
+            List<Pair<PrinterInfo, Location>> favoritePrinters) {
         List<PrinterInfo> printers = new ArrayList<>();
 
-        // Add the updated favorite printers.
+        // Store the printerIds that have already been added. We cannot compare the printerInfos in
+        // "printers" as they might have been taken from discoveredPrinters and the printerInfo does
+        // not equals() anymore
+        HashSet<PrinterId> alreadyAddedPrinter = new HashSet<>(MAX_FAVORITE_PRINTER_COUNT);
+
+        Location location = getCurrentLocation();
+
+        // Add the favorite printers that have last been used close to the current location
         final int favoritePrinterCount = favoritePrinters.size();
-        for (int i = 0; i < favoritePrinterCount; i++) {
-            PrinterInfo favoritePrinter = favoritePrinters.get(i);
-            PrinterInfo updatedPrinter = discoveredPrinters.remove(
-                    favoritePrinter.getId());
-            if (updatedPrinter != null) {
-                printers.add(updatedPrinter);
-            } else {
-                printers.add(favoritePrinter);
+        if (location != null) {
+            for (int i = 0; i < favoritePrinterCount; i++) {
+                // Only add a certain amount of favorite printers
+                if (printers.size() == MAX_FAVORITE_PRINTER_COUNT) {
+                    break;
+                }
+
+                PrinterInfo favoritePrinter = favoritePrinters.get(i).first;
+                Location printerLocation = favoritePrinters.get(i).second;
+
+                if (printerLocation != null
+                        && !alreadyAddedPrinter.contains(favoritePrinter.getId())) {
+                    if (printerLocation.distanceTo(location) <= MAX_PRINTER_DISTANCE) {
+                        updateAndAddPrinter(printers, favoritePrinter, discoveredPrinters);
+                        alreadyAddedPrinter.add(favoritePrinter.getId());
+                    }
+                }
             }
         }
 
-        // Add other updated printers.
+        // Add the other favorite printers
+        for (int i = 0; i < favoritePrinterCount; i++) {
+            // Only add a certain amount of favorite printers
+            if (printers.size() == MAX_FAVORITE_PRINTER_COUNT) {
+                break;
+            }
+
+            PrinterInfo favoritePrinter = favoritePrinters.get(i).first;
+            if (!alreadyAddedPrinter.contains(favoritePrinter.getId())) {
+                updateAndAddPrinter(printers, favoritePrinter, discoveredPrinters);
+            }
+        }
+
+        // Add other updated printers. Printers that have already been added have been removed from
+        // discoveredPrinters in the calls to updateAndAddPrinter
         final int printerCount = mPrinters.size();
         for (int i = 0; i < printerCount; i++) {
             PrinterInfo printer = mPrinters.get(i);
@@ -142,6 +243,21 @@
         if (DEBUG) {
             Log.i(LOG_TAG, "onStartLoading() " + FusedPrintersProvider.this.hashCode());
         }
+
+        mLocationManager.requestLocationUpdates(LocationRequest.create()
+                .setQuality(LocationRequest.POWER_LOW).setInterval(LOCATION_UPDATE_MS), this,
+                Looper.getMainLooper());
+
+        Location lastLocation = mLocationManager.getLastLocation();
+        if (lastLocation != null) {
+            onLocationChanged(lastLocation);
+        }
+
+        // Jumpstart location with a single forced update
+        Criteria oneTimeCriteria = new Criteria();
+        oneTimeCriteria.setAccuracy(Criteria.ACCURACY_FINE);
+        mLocationManager.requestSingleUpdate(oneTimeCriteria, this, Looper.getMainLooper());
+
         // The contract is that if we already have a valid,
         // result the we have to deliver it immediately.
         if (!mPrinters.isEmpty()) {
@@ -158,6 +274,8 @@
             Log.i(LOG_TAG, "onStopLoading() " + FusedPrintersProvider.this.hashCode());
         }
         onCancelLoad();
+
+        mLocationManager.removeUpdates(this);
     }
 
     @Override
@@ -188,28 +306,32 @@
                                 + " " + FusedPrintersProvider.this.hashCode());
                     }
 
-                    updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters);
+                    updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters,
+                            getCurrentLocation());
                 }
             });
             final int favoriteCount = mFavoritePrinters.size();
             List<PrinterId> printerIds = new ArrayList<>(favoriteCount);
             for (int i = 0; i < favoriteCount; i++) {
-                printerIds.add(mFavoritePrinters.get(i).getId());
+                printerIds.add(mFavoritePrinters.get(i).first.getId());
             }
             mDiscoverySession.startPrinterDiscovery(printerIds);
             List<PrinterInfo> printers = mDiscoverySession.getPrinters();
-            if (!printers.isEmpty()) {
-                updatePrinters(printers, mFavoritePrinters);
-            }
+
+            updatePrinters(printers, mFavoritePrinters, getCurrentLocation());
         }
     }
 
-    private void updatePrinters(List<PrinterInfo> printers, List<PrinterInfo> favoritePrinters) {
+    private void updatePrinters(List<PrinterInfo> printers,
+            List<Pair<PrinterInfo, Location>> favoritePrinters,
+            Location location) {
         if (mPrintersUpdatedBefore && mPrinters.equals(printers)
-                && mFavoritePrinters.equals(favoritePrinters)) {
+                && mFavoritePrinters.equals(favoritePrinters)
+                && Objects.equals(mLocationOfLastPrinterUpdate, location)) {
             return;
         }
 
+        mLocationOfLastPrinterUpdate = location;
         mPrintersUpdatedBefore = true;
 
         // Some of the found printers may have be a printer that is in the
@@ -271,6 +393,60 @@
         onStopLoading();
     }
 
+    /**
+     * Check if the location is acceptable. This is to filter out excessively old or inaccurate
+     * location updates.
+     *
+     * @param location the location to check
+     * @return true iff the location is usable.
+     */
+    private boolean isLocationAcceptable(Location location) {
+        return location != null
+                && location.getElapsedRealtimeNanos() > SystemClock.elapsedRealtimeNanos()
+                        - MAX_LOCATION_AGE_MS * 1000_000L
+                && location.hasAccuracy()
+                && location.getAccuracy() < MIN_LOCATION_ACCURACY;
+    }
+
+    @Override
+    public void onLocationChanged(Location location) {
+        synchronized(mLocationLock) {
+            // We expect the user to not move too fast while printing. Hence prefer more accurate
+            // updates over more recent ones for LOCATION_UPDATE_MS. We add a 10% fudge factor here
+            // as the location provider might send an update slightly too early.
+            if (isLocationAcceptable(location)
+                    && !location.equals(mLocation)
+                    && (mLocation == null
+                            || location
+                                    .getElapsedRealtimeNanos() > mLocation.getElapsedRealtimeNanos()
+                                            + LOCATION_UPDATE_MS * 0.9 * 1000_000L
+                            || (!mLocation.hasAccuracy()
+                                    || location.getAccuracy() < mLocation.getAccuracy()))) {
+                // Other callers of updatePrinters might want to know the location, hence cache it
+                mLocation = location;
+
+                if (areHistoricalPrintersLoaded()) {
+                    updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters, mLocation);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onStatusChanged(String provider, int status, Bundle extras) {
+        // nothing to do
+    }
+
+    @Override
+    public void onProviderEnabled(String provider) {
+        // nothing to do
+    }
+
+    @Override
+    public void onProviderDisabled(String provider) {
+        // nothing to do
+    }
+
     public boolean areHistoricalPrintersLoaded() {
         return mPersistenceManager.mReadHistoryCompleted;
     }
@@ -294,7 +470,7 @@
     public boolean isFavoritePrinter(PrinterId printerId) {
         final int printerCount = mFavoritePrinters.size();
         for (int i = 0; i < printerCount; i++) {
-            PrinterInfo favoritePritner = mFavoritePrinters.get(i);
+            PrinterInfo favoritePritner = mFavoritePrinters.get(i).first;
             if (favoritePritner.getId().equals(printerId)) {
                 return true;
             }
@@ -303,28 +479,22 @@
     }
 
     public void forgetFavoritePrinter(PrinterId printerId) {
-        List<PrinterInfo> newFavoritePrinters = null;
+        final int favoritePrinterCount = mFavoritePrinters.size();
+        List<Pair<PrinterInfo, Location>> newFavoritePrinters = new ArrayList<>(
+                favoritePrinterCount - 1);
 
         // Remove the printer from the favorites.
-        final int favoritePrinterCount = mFavoritePrinters.size();
         for (int i = 0; i < favoritePrinterCount; i++) {
-            PrinterInfo favoritePrinter = mFavoritePrinters.get(i);
-            if (favoritePrinter.getId().equals(printerId)) {
-                newFavoritePrinters = new ArrayList<>();
-                newFavoritePrinters.addAll(mPrinters);
-                newFavoritePrinters.remove(i);
-                break;
+            if (!mFavoritePrinters.get(i).first.getId().equals(printerId)) {
+                newFavoritePrinters.add(mFavoritePrinters.get(i));
             }
         }
 
-        // If we removed a favorite printer, we have work to do.
-        if (newFavoritePrinters != null) {
-            // Remove the printer from history and persist the latter.
-            mPersistenceManager.removeHistoricalPrinterAndWritePrinterHistory(printerId);
+        // Remove the printer from history and persist the latter.
+        mPersistenceManager.removeHistoricalPrinterAndWritePrinterHistory(printerId);
 
-            // Recompute and deliver the printers.
-            updatePrinters(mDiscoverySession.getPrinters(), newFavoritePrinters);
-        }
+        // Recompute and deliver the printers.
+        updatePrinters(mDiscoverySession.getPrinters(), newFavoritePrinters, getCurrentLocation());
     }
 
     private final class PersistenceManager {
@@ -333,17 +503,22 @@
         private static final String TAG_PRINTERS = "printers";
 
         private static final String TAG_PRINTER = "printer";
+        private static final String TAG_LOCATION = "location";
         private static final String TAG_PRINTER_ID = "printerId";
 
         private static final String ATTR_LOCAL_ID = "localId";
         private static final String ATTR_SERVICE_NAME = "serviceName";
 
+        private static final String ATTR_LONGITUDE = "longitude";
+        private static final String ATTR_LATITUDE = "latitude";
+        private static final String ATTR_ACCURACY = "accuracy";
+
         private static final String ATTR_NAME = "name";
         private static final String ATTR_DESCRIPTION = "description";
 
         private final AtomicFile mStatePersistFile;
 
-        private List<PrinterInfo> mHistoricalPrinters = new ArrayList<>();
+        private List<Pair<PrinterInfo, Location>> mHistoricalPrinters = new ArrayList<>();
 
         private boolean mReadHistoryCompleted;
 
@@ -402,7 +577,7 @@
             boolean writeHistory = false;
             final int printerCount = mHistoricalPrinters.size();
             for (int i = 0; i < printerCount; i++) {
-                PrinterInfo historicalPrinter = mHistoricalPrinters.get(i);
+                PrinterInfo historicalPrinter = mHistoricalPrinters.get(i).first;
 
                 if (!historicalPrinter.getId().equals(printer.getId())) {
                     continue;
@@ -414,7 +589,8 @@
                     continue;
                 }
 
-                mHistoricalPrinters.set(i, printer);
+                mHistoricalPrinters.set(i, new Pair<PrinterInfo, Location>(printer,
+                                mHistoricalPrinters.get(i).second));
 
                 // We only persist limited information in the printer history, hence check if
                 // we need to persist the update.
@@ -433,7 +609,14 @@
             if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) {
                 mHistoricalPrinters.remove(0);
             }
-            mHistoricalPrinters.add(printer);
+
+            Location location = getCurrentLocation();
+            if (!isLocationAcceptable(location)) {
+                location = null;
+            }
+
+            mHistoricalPrinters.add(new Pair<PrinterInfo, Location>(printer, location));
+
             writePrinterHistory();
         }
 
@@ -441,7 +624,7 @@
             boolean writeHistory = false;
             final int printerCount = mHistoricalPrinters.size();
             for (int i = printerCount - 1; i >= 0; i--) {
-                PrinterInfo historicalPrinter = mHistoricalPrinters.get(i);
+                PrinterInfo historicalPrinter = mHistoricalPrinters.get(i).first;
                 if (historicalPrinter.getId().equals(printerId)) {
                     mHistoricalPrinters.remove(i);
                     writeHistory = true;
@@ -462,63 +645,91 @@
             return mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified();
         }
 
-        private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) {
+        /**
+         * Sort the favorite printers by weight. If a printer is in the list multiple times for
+         * different locations, all instances are considered to have the accumulative weight. The
+         * actual favorite printers to display are computed in {@link #computeAndDeliverResult} as
+         * only at this time we know the location to use to determine if a printer is close enough
+         * to be preferred.
+         *
+         * @param printers The printers to sort.
+         * @return The sorted printers.
+         */
+        private List<Pair<PrinterInfo, Location>> sortFavoritePrinters(
+                List<Pair<PrinterInfo, Location>> printers) {
             Map<PrinterId, PrinterRecord> recordMap = new ArrayMap<>();
 
-            // Recompute the weights.
+            // Compute the weights.
             float currentWeight = 1.0f;
             final int printerCount = printers.size();
             for (int i = printerCount - 1; i >= 0; i--) {
-                PrinterInfo printer = printers.get(i);
-                // Aggregate weight for the same printer
-                PrinterRecord record = recordMap.get(printer.getId());
+                PrinterId printerId = printers.get(i).first.getId();
+                PrinterRecord record = recordMap.get(printerId);
                 if (record == null) {
-                    record = new PrinterRecord(printer);
-                    recordMap.put(printer.getId(), record);
+                    record = new PrinterRecord();
+                    recordMap.put(printerId, record);
                 }
+
+                record.printers.add(printers.get(i));
+
+                // Aggregate weight for the same printer
                 record.weight += currentWeight;
                 currentWeight *= WEIGHT_DECAY_COEFFICIENT;
             }
 
-            // Soft the favorite printers.
+            // Sort the favorite printers.
             List<PrinterRecord> favoriteRecords = new ArrayList<>(
                     recordMap.values());
             Collections.sort(favoriteRecords);
 
             // Write the favorites to the output.
-            final int favoriteCount = Math.min(favoriteRecords.size(),
-                    MAX_FAVORITE_PRINTER_COUNT);
-            List<PrinterInfo> favoritePrinters = new ArrayList<>(favoriteCount);
-            for (int i = 0; i < favoriteCount; i++) {
-                PrinterInfo printer = favoriteRecords.get(i).printer;
-                favoritePrinters.add(printer);
+            final int recordCount = favoriteRecords.size();
+            List<Pair<PrinterInfo, Location>> favoritePrinters = new ArrayList<>(printerCount);
+            for (int i = 0; i < recordCount; i++) {
+                favoritePrinters.addAll(favoriteRecords.get(i).printers);
             }
 
             return favoritePrinters;
         }
 
+        /**
+         * A set of printers with the same ID and the weight associated with them during
+         * {@link #sortFavoritePrinters}.
+         */
         private final class PrinterRecord implements Comparable<PrinterRecord> {
-            public final PrinterInfo printer;
+            /**
+             * The printers, all with the same ID, but potentially different properties or locations
+             */
+            public final List<Pair<PrinterInfo, Location>> printers;
+
+            /** The weight associated with the printers */
             public float weight;
 
-            public PrinterRecord(PrinterInfo printer) {
-                this.printer = printer;
+            /**
+             * Create a new record.
+             */
+            public PrinterRecord() {
+                printers = new ArrayList<>();
             }
 
+            /**
+             * Compare two records by weight.
+             */
             @Override
             public int compareTo(PrinterRecord another) {
                 return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight);
             }
         }
 
-        private final class ReadTask extends AsyncTask<Void, Void, List<PrinterInfo>> {
+        private final class ReadTask
+                extends AsyncTask<Void, Void, List<Pair<PrinterInfo, Location>>> {
             @Override
-            protected List<PrinterInfo> doInBackground(Void... args) {
+            protected List<Pair<PrinterInfo, Location>> doInBackground(Void... args) {
                return doReadPrinterHistory();
             }
 
             @Override
-            protected void onPostExecute(List<PrinterInfo> printers) {
+            protected void onPostExecute(List<Pair<PrinterInfo, Location>> printers) {
                 if (DEBUG) {
                     Log.i(LOG_TAG, "read history completed "
                             + FusedPrintersProvider.this.hashCode());
@@ -541,7 +752,8 @@
 
                 final int printerCount = printers.size();
                 for (int i = printerCount - 1; i >= 0; i--) {
-                    ComponentName printerServiceName = printers.get(i).getId().getServiceName();
+                    ComponentName printerServiceName = printers.get(i).first.getId()
+                            .getServiceName();
                     if (!enabledComponents.contains(printerServiceName)) {
                         printers.remove(i);
                     }
@@ -552,12 +764,13 @@
 
                 // Compute the favorite printers.
                 mFavoritePrinters.clear();
-                mFavoritePrinters.addAll(computeFavoritePrinters(mHistoricalPrinters));
+                mFavoritePrinters.addAll(sortFavoritePrinters(mHistoricalPrinters));
 
                 mReadHistoryCompleted = true;
 
                 // Deliver the printers.
-                updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters);
+                updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters,
+                        getCurrentLocation());
 
                 // We are done.
                 mReadTask = null;
@@ -567,12 +780,12 @@
             }
 
             @Override
-            protected void onCancelled(List<PrinterInfo> printerInfos) {
+            protected void onCancelled(List<Pair<PrinterInfo, Location>> printerInfos) {
                 // We are done.
                 mReadTask = null;
             }
 
-            private List<PrinterInfo> doReadPrinterHistory() {
+            private List<Pair<PrinterInfo, Location>> doReadPrinterHistory() {
                 final FileInputStream in;
                 try {
                     in = mStatePersistFile.openRead();
@@ -584,7 +797,7 @@
                     return new ArrayList<>();
                 }
                 try {
-                    List<PrinterInfo> printers = new ArrayList<>();
+                    List<Pair<PrinterInfo, Location>> printers = new ArrayList<>();
                     XmlPullParser parser = Xml.newPullParser();
                     parser.setInput(in, StandardCharsets.UTF_8.name());
                     parseState(parser, printers);
@@ -605,8 +818,9 @@
                 return Collections.emptyList();
             }
 
-            private void parseState(XmlPullParser parser, List<PrinterInfo> outPrinters)
-                    throws IOException, XmlPullParserException {
+            private void parseState(XmlPullParser parser,
+                    List<Pair<PrinterInfo, Location>> outPrinters)
+                            throws IOException, XmlPullParserException {
                 parser.next();
                 skipEmptyTextTags(parser);
                 expect(parser, XmlPullParser.START_TAG, TAG_PRINTERS);
@@ -624,8 +838,9 @@
                 expect(parser, XmlPullParser.END_TAG, TAG_PRINTERS);
             }
 
-            private boolean parsePrinter(XmlPullParser parser, List<PrinterInfo> outPrinters)
-                    throws IOException, XmlPullParserException {
+            private boolean parsePrinter(XmlPullParser parser,
+                    List<Pair<PrinterInfo, Location>> outPrinters)
+                            throws IOException, XmlPullParserException {
                 skipEmptyTextTags(parser);
                 if (!accept(parser, XmlPullParser.START_TAG, TAG_PRINTER)) {
                     return false;
@@ -647,6 +862,25 @@
                 expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
                 parser.next();
 
+                skipEmptyTextTags(parser);
+                Location location;
+                if (accept(parser, XmlPullParser.START_TAG, TAG_LOCATION)) {
+                    location = new Location("");
+                    location.setLongitude(
+                            Double.parseDouble(parser.getAttributeValue(null, ATTR_LONGITUDE)));
+                    location.setLatitude(
+                            Double.parseDouble(parser.getAttributeValue(null, ATTR_LATITUDE)));
+                    location.setAccuracy(
+                            Float.parseFloat(parser.getAttributeValue(null, ATTR_ACCURACY)));
+                    parser.next();
+
+                    skipEmptyTextTags(parser);
+                    expect(parser, XmlPullParser.END_TAG, TAG_LOCATION);
+                    parser.next();
+                } else {
+                    location = null;
+                }
+
                 // If the printer is available the printer will be replaced by the one read from the
                 // discovery session, hence the only time when this object is used is when the
                 // printer is unavailable.
@@ -655,7 +889,7 @@
                 builder.setDescription(description);
                 PrinterInfo printer = builder.build();
 
-                outPrinters.add(printer);
+                outPrinters.add(new Pair<PrinterInfo, Location>(printer, location));
 
                 if (DEBUG) {
                     Log.i(LOG_TAG, "[RESTORED] " + printer);
@@ -700,15 +934,16 @@
             }
         }
 
-        private final class WriteTask extends AsyncTask<List<PrinterInfo>, Void, Void> {
+        private final class WriteTask
+                extends AsyncTask<List<Pair<PrinterInfo, Location>>, Void, Void> {
             @Override
             protected Void doInBackground(
-                    @SuppressWarnings("unchecked") List<PrinterInfo>... printers) {
+                    @SuppressWarnings("unchecked") List<Pair<PrinterInfo, Location>>... printers) {
                 doWritePrinterHistory(printers[0]);
                 return null;
             }
 
-            private void doWritePrinterHistory(List<PrinterInfo> printers) {
+            private void doWritePrinterHistory(List<Pair<PrinterInfo, Location>> printers) {
                 FileOutputStream out = null;
                 try {
                     out = mStatePersistFile.startWrite();
@@ -720,7 +955,7 @@
 
                     final int printerCount = printers.size();
                     for (int i = 0; i < printerCount; i++) {
-                        PrinterInfo printer = printers.get(i);
+                        PrinterInfo printer = printers.get(i).first;
 
                         serializer.startTag(null, TAG_PRINTER);
 
@@ -737,6 +972,18 @@
                                 .flattenToString());
                         serializer.endTag(null, TAG_PRINTER_ID);
 
+                        Location location = printers.get(i).second;
+                        if (location != null) {
+                            serializer.startTag(null, TAG_LOCATION);
+                            serializer.attribute(null, ATTR_LONGITUDE,
+                                    String.valueOf(location.getLongitude()));
+                            serializer.attribute(null, ATTR_LATITUDE,
+                                    String.valueOf(location.getLatitude()));
+                            serializer.attribute(null, ATTR_ACCURACY,
+                                    String.valueOf(location.getAccuracy()));
+                            serializer.endTag(null, TAG_LOCATION);
+                        }
+
                         serializer.endTag(null, TAG_PRINTER);
 
                         if (DEBUG) {
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index f604bb7..b254f29 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -594,6 +594,14 @@
                 }
             }
 
+            // Print Spooler
+            PackageParser.Package printSpoolerPackage = getSystemPackageLPr(
+                    "com.android.printspooler");
+            if (printSpoolerPackage != null
+                    && doesPackageSupportRuntimePermissions(printSpoolerPackage)) {
+                grantRuntimePermissionsLPw(printSpoolerPackage, LOCATION_PERMISSIONS, true, userId);
+            }
+
             mService.mSettings.onDefaultRuntimePermissionsGrantedLPr(userId);
         }
     }