diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml index 937eb157268c7..af9d25196bec5 100644 --- a/packages/PrintSpooler/AndroidManifest.xml +++ b/packages/PrintSpooler/AndroidManifest.xml @@ -38,6 +38,8 @@ + + > { +public final class FusedPrintersProvider extends Loader> + implements LocationListener { private static final String LOG_TAG = "FusedPrintersProvider"; private static final boolean DEBUG = false; @@ -70,10 +81,22 @@ public final class FusedPrintersProvider extends Loader> { 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 mPrinters = new ArrayList<>(); - private final List mFavoritePrinters = + private final List> mFavoritePrinters = new ArrayList<>(); private final PersistenceManager mPersistenceManager; @@ -84,33 +107,111 @@ public final class FusedPrintersProvider extends Loader> { 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 dest, PrinterInfo printer, + Map 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 discoveredPrinters, - List favoritePrinters) { + List> favoritePrinters) { List 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 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 @@ public final class FusedPrintersProvider extends Loader> { 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 @@ public final class FusedPrintersProvider extends Loader> { Log.i(LOG_TAG, "onStopLoading() " + FusedPrintersProvider.this.hashCode()); } onCancelLoad(); + + mLocationManager.removeUpdates(this); } @Override @@ -188,28 +306,32 @@ public final class FusedPrintersProvider extends Loader> { + " " + FusedPrintersProvider.this.hashCode()); } - updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters); + updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters, + getCurrentLocation()); } }); final int favoriteCount = mFavoritePrinters.size(); List 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 printers = mDiscoverySession.getPrinters(); - if (!printers.isEmpty()) { - updatePrinters(printers, mFavoritePrinters); - } + + updatePrinters(printers, mFavoritePrinters, getCurrentLocation()); } } - private void updatePrinters(List printers, List favoritePrinters) { + private void updatePrinters(List printers, + List> 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 @@ public final class FusedPrintersProvider extends Loader> { 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 final class FusedPrintersProvider extends Loader> { 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 final class FusedPrintersProvider extends Loader> { } public void forgetFavoritePrinter(PrinterId printerId) { - List newFavoritePrinters = null; + final int favoritePrinterCount = mFavoritePrinters.size(); + List> 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 @@ public final class FusedPrintersProvider extends Loader> { 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 mHistoricalPrinters = new ArrayList<>(); + private List> mHistoricalPrinters = new ArrayList<>(); private boolean mReadHistoryCompleted; @@ -402,7 +577,7 @@ public final class FusedPrintersProvider extends Loader> { 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 @@ public final class FusedPrintersProvider extends Loader> { continue; } - mHistoricalPrinters.set(i, printer); + mHistoricalPrinters.set(i, new Pair(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 @@ public final class FusedPrintersProvider extends Loader> { if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) { mHistoricalPrinters.remove(0); } - mHistoricalPrinters.add(printer); + + Location location = getCurrentLocation(); + if (!isLocationAcceptable(location)) { + location = null; + } + + mHistoricalPrinters.add(new Pair(printer, location)); + writePrinterHistory(); } @@ -441,7 +624,7 @@ public final class FusedPrintersProvider extends Loader> { 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 @@ public final class FusedPrintersProvider extends Loader> { return mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified(); } - private List computeFavoritePrinters(List 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> sortFavoritePrinters( + List> printers) { Map 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 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 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> 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 { - public final PrinterInfo printer; + /** + * The printers, all with the same ID, but potentially different properties or locations + */ + public final List> 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> { + private final class ReadTask + extends AsyncTask>> { @Override - protected List doInBackground(Void... args) { + protected List> doInBackground(Void... args) { return doReadPrinterHistory(); } @Override - protected void onPostExecute(List printers) { + protected void onPostExecute(List> printers) { if (DEBUG) { Log.i(LOG_TAG, "read history completed " + FusedPrintersProvider.this.hashCode()); @@ -541,7 +752,8 @@ public final class FusedPrintersProvider extends Loader> { 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 @@ public final class FusedPrintersProvider extends Loader> { // 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 @@ public final class FusedPrintersProvider extends Loader> { } @Override - protected void onCancelled(List printerInfos) { + protected void onCancelled(List> printerInfos) { // We are done. mReadTask = null; } - private List doReadPrinterHistory() { + private List> doReadPrinterHistory() { final FileInputStream in; try { in = mStatePersistFile.openRead(); @@ -584,7 +797,7 @@ public final class FusedPrintersProvider extends Loader> { return new ArrayList<>(); } try { - List printers = new ArrayList<>(); + List> printers = new ArrayList<>(); XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, StandardCharsets.UTF_8.name()); parseState(parser, printers); @@ -605,8 +818,9 @@ public final class FusedPrintersProvider extends Loader> { return Collections.emptyList(); } - private void parseState(XmlPullParser parser, List outPrinters) - throws IOException, XmlPullParserException { + private void parseState(XmlPullParser parser, + List> outPrinters) + throws IOException, XmlPullParserException { parser.next(); skipEmptyTextTags(parser); expect(parser, XmlPullParser.START_TAG, TAG_PRINTERS); @@ -624,8 +838,9 @@ public final class FusedPrintersProvider extends Loader> { expect(parser, XmlPullParser.END_TAG, TAG_PRINTERS); } - private boolean parsePrinter(XmlPullParser parser, List outPrinters) - throws IOException, XmlPullParserException { + private boolean parsePrinter(XmlPullParser parser, + List> outPrinters) + throws IOException, XmlPullParserException { skipEmptyTextTags(parser); if (!accept(parser, XmlPullParser.START_TAG, TAG_PRINTER)) { return false; @@ -647,6 +862,25 @@ public final class FusedPrintersProvider extends Loader> { 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 @@ public final class FusedPrintersProvider extends Loader> { builder.setDescription(description); PrinterInfo printer = builder.build(); - outPrinters.add(printer); + outPrinters.add(new Pair(printer, location)); if (DEBUG) { Log.i(LOG_TAG, "[RESTORED] " + printer); @@ -700,15 +934,16 @@ public final class FusedPrintersProvider extends Loader> { } } - private final class WriteTask extends AsyncTask, Void, Void> { + private final class WriteTask + extends AsyncTask>, Void, Void> { @Override protected Void doInBackground( - @SuppressWarnings("unchecked") List... printers) { + @SuppressWarnings("unchecked") List>... printers) { doWritePrinterHistory(printers[0]); return null; } - private void doWritePrinterHistory(List printers) { + private void doWritePrinterHistory(List> printers) { FileOutputStream out = null; try { out = mStatePersistFile.startWrite(); @@ -720,7 +955,7 @@ public final class FusedPrintersProvider extends Loader> { 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 @@ public final class FusedPrintersProvider extends Loader> { .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 f604bb781452b..b254f29e0d5a3 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 @@ final class DefaultPermissionGrantPolicy { } } + // Print Spooler + PackageParser.Package printSpoolerPackage = getSystemPackageLPr( + "com.android.printspooler"); + if (printSpoolerPackage != null + && doesPackageSupportRuntimePermissions(printSpoolerPackage)) { + grantRuntimePermissionsLPw(printSpoolerPackage, LOCATION_PERMISSIONS, true, userId); + } + mService.mSettings.onDefaultRuntimePermissionsGrantedLPr(userId); } }