Merge "Warn that SettingInjectorService must be fast" into klp-dev
diff --git a/Android.mk b/Android.mk
index 8e59ffa..cae9b92 100644
--- a/Android.mk
+++ b/Android.mk
@@ -162,8 +162,6 @@
core/java/android/os/IVibratorService.aidl \
core/java/android/service/notification/INotificationListener.aidl \
core/java/android/print/ILayoutResultCallback.aidl \
- core/java/android/print/IPrinterDiscoverySessionController.aidl \
- core/java/android/print/IPrinterDiscoverySessionObserver.aidl \
core/java/android/print/IPrintDocumentAdapter.aidl \
core/java/android/print/IPrintClient.aidl \
core/java/android/print/IPrintManager.aidl \
@@ -264,7 +262,7 @@
telephony/java/com/android/internal/telephony/IWapPushManager.aidl \
wifi/java/android/net/wifi/IWifiManager.aidl \
wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \
- packages/services/Proxy/com/android/net/IProxyService.aidl \
+ packages/services/PacProcessor/com/android/net/IProxyService.aidl \
# FRAMEWORKS_BASE_JAVA_SRC_DIRS comes from build/core/pathmap.mk
LOCAL_AIDL_INCLUDES += $(FRAMEWORKS_BASE_JAVA_SRC_DIRS)
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 8fd5186..a94252d 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -170,6 +170,7 @@
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/print/IPrinterDiscoveryObserver.*)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/print/)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/core/java/android/printservice/)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework-base_intermediates/src/packages/services/Proxy/)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
diff --git a/api/current.txt b/api/current.txt
index 2ae7af1..0d4dccf 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5014,7 +5014,8 @@
}
public final class BluetoothGatt implements android.bluetooth.BluetoothProfile {
- method public void abortReliableWrite(android.bluetooth.BluetoothDevice);
+ method public void abortReliableWrite();
+ method public deprecated void abortReliableWrite(android.bluetooth.BluetoothDevice);
method public boolean beginReliableWrite();
method public void close();
method public boolean connect();
@@ -12797,6 +12798,7 @@
public static class MediaPlayer.TrackInfo implements android.os.Parcelable {
method public int describeContents();
+ method public android.media.MediaFormat getFormat();
method public java.lang.String getLanguage();
method public int getTrackType();
method public void writeToParcel(android.os.Parcel, int);
@@ -12858,6 +12860,7 @@
field public static final int CAMCORDER = 5; // 0x5
field public static final int DEFAULT = 0; // 0x0
field public static final int MIC = 1; // 0x1
+ field public static final int REMOTE_SUBMIX = 8; // 0x8
field public static final int VOICE_CALL = 4; // 0x4
field public static final int VOICE_COMMUNICATION = 7; // 0x7
field public static final int VOICE_DOWNLINK = 3; // 0x3
@@ -18619,6 +18622,7 @@
method protected android.view.View onCreateView(android.view.ViewGroup);
method public void onDependencyChanged(android.preference.Preference, boolean);
method protected java.lang.Object onGetDefaultValue(android.content.res.TypedArray, int);
+ method public void onParentChanged(android.preference.Preference, boolean);
method protected void onPrepareForRemoval();
method protected void onRestoreInstanceState(android.os.Parcelable);
method protected android.os.Parcelable onSaveInstanceState();
@@ -19036,7 +19040,7 @@
method public android.print.PrintAttributes getAttributes();
method public int getCopies();
method public int getId();
- method public java.lang.CharSequence getLabel();
+ method public java.lang.String getLabel();
method public android.print.PageRange[] getPages();
method public android.print.PrinterId getPrinterId();
method public int getState();
@@ -19160,7 +19164,7 @@
public final class PrintJob {
method public boolean cancel();
method public boolean complete();
- method public boolean fail(java.lang.CharSequence);
+ method public boolean fail(java.lang.String);
method public android.printservice.PrintDocument getDocument();
method public int getId();
method public android.print.PrintJobInfo getInfo();
@@ -19189,11 +19193,15 @@
}
public abstract class PrinterDiscoverySession {
- ctor public PrinterDiscoverySession(android.content.Context);
+ ctor public PrinterDiscoverySession();
method public final void addPrinters(java.util.List<android.print.PrinterInfo>);
- method public abstract void onClose();
- method public abstract void onOpen(java.util.List<android.print.PrinterId>);
+ method public final java.util.List<android.print.PrinterInfo> getPrinters();
+ method public final boolean isDestroyed();
+ method public final boolean isPrinterDiscoveryStarted();
+ method public abstract void onDestroy();
method public abstract void onRequestPrinterUpdate(android.print.PrinterId);
+ method public abstract void onStartPrinterDiscovery(java.util.List<android.print.PrinterId>);
+ method public abstract void onStopPrinterDiscovery();
method public final void removePrinters(java.util.List<android.print.PrinterId>);
method public final void updatePrinters(java.util.List<android.print.PrinterInfo>);
}
@@ -21139,16 +21147,20 @@
method public static float getFloat(android.content.ContentResolver, java.lang.String) throws android.provider.Settings.SettingNotFoundException;
method public static int getInt(android.content.ContentResolver, java.lang.String, int);
method public static int getInt(android.content.ContentResolver, java.lang.String) throws android.provider.Settings.SettingNotFoundException;
+ method public static final int getLocationMode(android.content.ContentResolver);
+ method public static final int getLocationModeForUser(android.content.ContentResolver, int);
method public static long getLong(android.content.ContentResolver, java.lang.String, long);
method public static long getLong(android.content.ContentResolver, java.lang.String) throws android.provider.Settings.SettingNotFoundException;
method public static java.lang.String getString(android.content.ContentResolver, java.lang.String);
method public static android.net.Uri getUriFor(java.lang.String);
- method public static final boolean isLocationProviderEnabled(android.content.ContentResolver, java.lang.String);
+ method public static final deprecated boolean isLocationProviderEnabled(android.content.ContentResolver, java.lang.String);
method public static boolean putFloat(android.content.ContentResolver, java.lang.String, float);
method public static boolean putInt(android.content.ContentResolver, java.lang.String, int);
method public static boolean putLong(android.content.ContentResolver, java.lang.String, long);
method public static boolean putString(android.content.ContentResolver, java.lang.String, java.lang.String);
- method public static final void setLocationProviderEnabled(android.content.ContentResolver, java.lang.String, boolean);
+ method public static final void setLocationMode(android.content.ContentResolver, int);
+ method public static final void setLocationModeForUser(android.content.ContentResolver, int, int);
+ method public static final deprecated void setLocationProviderEnabled(android.content.ContentResolver, java.lang.String, boolean);
field public static final java.lang.String ACCESSIBILITY_ENABLED = "accessibility_enabled";
field public static final java.lang.String ACCESSIBILITY_SPEAK_PASSWORD = "speak_password";
field public static final deprecated java.lang.String ADB_ENABLED = "adb_enabled";
@@ -21167,6 +21179,10 @@
field public static final deprecated java.lang.String HTTP_PROXY = "http_proxy";
field public static final java.lang.String INPUT_METHOD_SELECTOR_VISIBILITY = "input_method_selector_visibility";
field public static final deprecated java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
+ field public static final int LOCATION_MODE_BATTERY_SAVING = 2; // 0x2
+ field public static final int LOCATION_MODE_HIGH_ACCURACY = 3; // 0x3
+ field public static final int LOCATION_MODE_OFF = 0; // 0x0
+ field public static final int LOCATION_MODE_SENSORS_ONLY = 1; // 0x1
field public static final java.lang.String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed";
field public static final java.lang.String LOCK_PATTERN_ENABLED = "lock_pattern_autolock";
field public static final deprecated java.lang.String LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED = "lock_pattern_tactile_feedback_enabled";
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 8f2f9ca..e02410a 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -747,6 +747,7 @@
int mResultCode = RESULT_CANCELED;
Intent mResultData = null;
private TranslucentConversionListener mTranslucentCallback;
+ private boolean mChangeCanvasToTranslucent;
private boolean mTitleReady = false;
@@ -4903,7 +4904,9 @@
public void convertFromTranslucent() {
try {
mTranslucentCallback = null;
- ActivityManagerNative.getDefault().convertFromTranslucent(mToken);
+ if (ActivityManagerNative.getDefault().convertFromTranslucent(mToken)) {
+ WindowManagerGlobal.getInstance().changeCanvasOpacity(mToken, true);
+ }
} catch (RemoteException e) {
// pass
}
@@ -4931,7 +4934,8 @@
public void convertToTranslucent(TranslucentConversionListener callback) {
try {
mTranslucentCallback = callback;
- ActivityManagerNative.getDefault().convertToTranslucent(mToken);
+ mChangeCanvasToTranslucent =
+ ActivityManagerNative.getDefault().convertToTranslucent(mToken);
} catch (RemoteException e) {
// pass
}
@@ -4943,6 +4947,9 @@
mTranslucentCallback.onTranslucentConversionComplete(drawComplete);
mTranslucentCallback = null;
}
+ if (mChangeCanvasToTranslucent) {
+ WindowManagerGlobal.getInstance().changeCanvasOpacity(mToken, false);
+ }
}
/**
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index c65f17e..6ac2e80 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -438,7 +438,7 @@
/** @hide */
public static boolean isLowRamDeviceStatic() {
- return Resources.getSystem().getBoolean(com.android.internal.R.bool.config_lowRamDevice);
+ return "true".equals(SystemProperties.get("ro.config.low_ram", "false"));
}
/**
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 6a29552..6d72114 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1502,16 +1502,18 @@
case CONVERT_FROM_TRANSLUCENT_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
- convertFromTranslucent(token);
+ boolean converted = convertFromTranslucent(token);
reply.writeNoException();
+ reply.writeInt(converted ? 1 : 0);
return true;
}
case CONVERT_TO_TRANSLUCENT_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
- convertToTranslucent(token);
+ boolean converted = convertToTranslucent(token);
reply.writeNoException();
+ reply.writeInt(converted ? 1 : 0);
return true;
}
@@ -3876,7 +3878,7 @@
reply.recycle();
}
- public void convertFromTranslucent(IBinder token)
+ public boolean convertFromTranslucent(IBinder token)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -3884,11 +3886,13 @@
data.writeStrongBinder(token);
mRemote.transact(CONVERT_FROM_TRANSLUCENT_TRANSACTION, data, reply, 0);
reply.readException();
+ boolean res = reply.readInt() != 0;
data.recycle();
reply.recycle();
+ return res;
}
- public void convertToTranslucent(IBinder token)
+ public boolean convertToTranslucent(IBinder token)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -3896,8 +3900,10 @@
data.writeStrongBinder(token);
mRemote.transact(CONVERT_TO_TRANSLUCENT_TRANSACTION, data, reply, 0);
reply.readException();
+ boolean res = reply.readInt() != 0;
data.recycle();
reply.recycle();
+ return res;
}
public void setImmersive(IBinder token, boolean immersive)
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 6d1b6fc..af9a245 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -301,8 +301,8 @@
public void finishHeavyWeightApp() throws RemoteException;
- public void convertFromTranslucent(IBinder token) throws RemoteException;
- public void convertToTranslucent(IBinder token) throws RemoteException;
+ public boolean convertFromTranslucent(IBinder token) throws RemoteException;
+ public boolean convertToTranslucent(IBinder token) throws RemoteException;
public void notifyActivityDrawn(IBinder token) throws RemoteException;
public void setImmersive(IBinder token, boolean immersive) throws RemoteException;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index be831d7..e0b1c00 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -32,10 +32,17 @@
import android.os.UserHandle;
import android.util.Log;
+import com.android.org.conscrypt.TrustedCertificateStore;
+
+import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
import java.util.List;
+import java.util.Set;
/**
* Public interface for managing policies enforced on a device. Most clients
@@ -1328,6 +1335,70 @@
}
/**
+ * Installs the given certificate as a User CA.
+ *
+ * @return false if the certBuffer cannot be parsed or installation is
+ * interrupted, otherwise true
+ * @hide
+ */
+ public boolean installCaCert(byte[] certBuffer) {
+ if (mService != null) {
+ try {
+ return mService.installCaCert(certBuffer);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Uninstalls the given certificate from the list of User CAs, if present.
+ *
+ * @hide
+ */
+ public void uninstallCaCert(byte[] certBuffer) {
+ if (mService != null) {
+ try {
+ mService.uninstallCaCert(certBuffer);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed talking with device policy service", e);
+ }
+ }
+ }
+
+ /**
+ * Returns whether there are any user-installed CA certificates.
+ *
+ * @hide
+ */
+ public boolean hasAnyCaCertsInstalled() {
+ TrustedCertificateStore certStore = new TrustedCertificateStore();
+ Set<String> aliases = certStore.userAliases();
+ return aliases != null && !aliases.isEmpty();
+ }
+
+ /**
+ * Returns whether this certificate has been installed as a User CA.
+ *
+ * @hide
+ */
+ public boolean hasCaCertInstalled(byte[] certBuffer) {
+ TrustedCertificateStore certStore = new TrustedCertificateStore();
+ String alias;
+ byte[] pemCert;
+ try {
+ CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ X509Certificate cert = (X509Certificate) certFactory.generateCertificate(
+ new ByteArrayInputStream(certBuffer));
+ return certStore.getCertificateAlias(cert) != null;
+ } catch (CertificateException ce) {
+ Log.w(TAG, "Could not parse certificate", ce);
+ }
+ return false;
+ }
+
+ /**
* Called by an application that is administering the device to disable all cameras
* on the device. After setting this, no applications will be able to access any cameras
* on the device.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 9659a91..172c47c 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -102,4 +102,7 @@
boolean isDeviceOwner(String packageName);
String getDeviceOwner();
String getDeviceOwnerName();
+
+ boolean installCaCert(in byte[] certBuffer);
+ void uninstallCaCert(in byte[] certBuffer);
}
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
index a8c310b..b390aa1 100644
--- a/core/java/android/bluetooth/BluetoothGatt.java
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -1102,7 +1102,7 @@
*
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*/
- public void abortReliableWrite(BluetoothDevice mDevice) {
+ public void abortReliableWrite() {
if (DBG) Log.d(TAG, "abortReliableWrite() - device: " + mDevice.getAddress());
if (mService == null || mClientIf == 0) return;
@@ -1114,6 +1114,13 @@
}
/**
+ * @deprecated Use {@link #abortReliableWrite()}
+ */
+ public void abortReliableWrite(BluetoothDevice mDevice) {
+ abortReliableWrite();
+ }
+
+ /**
* Enable or disable notifications/indications for a given characteristic.
*
* <p>Once notifications are enabled for a characteristic, a
diff --git a/core/java/android/hardware/camera2/CameraPropertiesKeys.java b/core/java/android/hardware/camera2/CameraPropertiesKeys.java
deleted file mode 100644
index 41b31ff..0000000
--- a/core/java/android/hardware/camera2/CameraPropertiesKeys.java
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright (C) 2013 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 android.hardware.camera2;
-
-import static android.hardware.camera2.CameraMetadata.Key;
-
-/**
- * ! Do not edit this file directly !
- *
- * Generated automatically from CameraPropertiesKeys.mako
- *
- * TODO: Include a hash of the input files here that the build can check.
- */
-
-/**
- * The base class for camera controls and information.
- *
- * This class defines the basic key/value map used for querying for camera
- * characteristics or capture results, and for setting camera request
- * parameters.
- *
- * @see CameraProperties
- * @see CameraMetadata
- * @hide
- **/
-public final class CameraPropertiesKeys {
- public static final class Control {
- public static final Key<byte[]> AE_AVAILABLE_ANTIBANDING_MODES =
- new Key<byte[]>("android.control.aeAvailableAntibandingModes", byte[].class);
- public static final Key<int[]> AE_AVAILABLE_TARGET_FPS_RANGES =
- new Key<int[]>("android.control.aeAvailableTargetFpsRanges", int[].class);
- public static final Key<int[]> AE_COMPENSATION_RANGE =
- new Key<int[]>("android.control.aeCompensationRange", int[].class);
- public static final Key<Rational> AE_COMPENSATION_STEP =
- new Key<Rational>("android.control.aeCompensationStep", Rational.class);
- public static final Key<byte[]> AF_AVAILABLE_MODES =
- new Key<byte[]>("android.control.afAvailableModes", byte[].class);
- public static final Key<byte[]> AVAILABLE_EFFECTS =
- new Key<byte[]>("android.control.availableEffects", byte[].class);
- public static final Key<byte[]> AVAILABLE_SCENE_MODES =
- new Key<byte[]>("android.control.availableSceneModes", byte[].class);
- public static final Key<byte[]> AVAILABLE_VIDEO_STABILIZATION_MODES =
- new Key<byte[]>("android.control.availableVideoStabilizationModes", byte[].class);
- public static final Key<byte[]> AWB_AVAILABLE_MODES =
- new Key<byte[]>("android.control.awbAvailableModes", byte[].class);
- public static final Key<Integer> MAX_REGIONS =
- new Key<Integer>("android.control.maxRegions", int.class);
-
- }
-
- public static final class Flash {
- public static final class Info {
- public static final Key<Byte> AVAILABLE =
- new Key<Byte>("android.flash.info.available", byte.class);
- }
-
- }
-
- public static final class Jpeg {
- public static final Key<android.hardware.camera2.Size[]> AVAILABLE_THUMBNAIL_SIZES =
- new Key<android.hardware.camera2.Size[]>("android.jpeg.availableThumbnailSizes", android.hardware.camera2.Size[].class);
-
- }
-
- public static final class Lens {
- public static final class Info {
- public static final Key<float[]> AVAILABLE_APERTURES =
- new Key<float[]>("android.lens.info.availableApertures", float[].class);
- public static final Key<float[]> AVAILABLE_FILTER_DENSITIES =
- new Key<float[]>("android.lens.info.availableFilterDensities", float[].class);
- public static final Key<float[]> AVAILABLE_FOCAL_LENGTHS =
- new Key<float[]>("android.lens.info.availableFocalLengths", float[].class);
- public static final Key<byte[]> AVAILABLE_OPTICAL_STABILIZATION =
- new Key<byte[]>("android.lens.info.availableOpticalStabilization", byte[].class);
- public static final Key<Float> HYPERFOCAL_DISTANCE =
- new Key<Float>("android.lens.info.hyperfocalDistance", float.class);
- public static final Key<Float> MINIMUM_FOCUS_DISTANCE =
- new Key<Float>("android.lens.info.minimumFocusDistance", float.class);
- public static final Key<android.hardware.camera2.Size> SHADING_MAP_SIZE =
- new Key<android.hardware.camera2.Size>("android.lens.info.shadingMapSize", android.hardware.camera2.Size.class);
- }
-
- public static final class FacingKey extends Key<Lens.FacingKey.Enum> {
- public enum Enum {
- FRONT,
- BACK;
- }
-
- public static final Enum FRONT = Enum.FRONT;
- public static final Enum BACK = Enum.BACK;
-
- // TODO: remove requirement for constructor by making Key an interface
- private FacingKey(String name) {
- super(name, Lens.FacingKey.Enum.class);
- }
-
- }
-
- public static final Key<Lens.FacingKey.Enum> FACING =
- new FacingKey("android.lens.facing");
-
- }
-
- public static final class Request {
- public static final Key<int[]> MAX_NUM_OUTPUT_STREAMS =
- new Key<int[]>("android.request.maxNumOutputStreams", int[].class);
-
- }
-
- public static final class Scaler {
-
- public static final class AvailableFormatsKey extends Key<Scaler.AvailableFormatsKey.Enum[]> {
- public enum Enum {
- RAW_SENSOR,
- YV12,
- YCrCb_420_SP,
- IMPLEMENTATION_DEFINED,
- YCbCr_420_888,
- BLOB;
- }
-
- public static final Enum RAW_SENSOR = Enum.RAW_SENSOR;
- public static final Enum YV12 = Enum.YV12;
- public static final Enum YCrCb_420_SP = Enum.YCrCb_420_SP;
- public static final Enum IMPLEMENTATION_DEFINED = Enum.IMPLEMENTATION_DEFINED;
- public static final Enum YCbCr_420_888 = Enum.YCbCr_420_888;
- public static final Enum BLOB = Enum.BLOB;
-
- // TODO: remove requirement for constructor by making Key an interface
- private AvailableFormatsKey(String name) {
- super(name, Scaler.AvailableFormatsKey.Enum[].class);
- }
-
- static {
- CameraMetadata.registerEnumValues(Scaler.AvailableFormatsKey.Enum.class, new int[] {
- 0x20, // RAW_SENSOR
- 0x32315659, // YV12
- 0x11, // YCrCb_420_SP
- 0x22, // IMPLEMENTATION_DEFINED
- 0x23, // YCbCr_420_888
- 0x21 // BLOB
- });
- }
- }
-
- public static final Key<Scaler.AvailableFormatsKey.Enum[]> AVAILABLE_FORMATS =
- new AvailableFormatsKey("android.scaler.availableFormats");
- public static final Key<long[]> AVAILABLE_JPEG_MIN_DURATIONS =
- new Key<long[]>("android.scaler.availableJpegMinDurations", long[].class);
- public static final Key<android.hardware.camera2.Size[]> AVAILABLE_JPEG_SIZES =
- new Key<android.hardware.camera2.Size[]>("android.scaler.availableJpegSizes", android.hardware.camera2.Size[].class);
- public static final Key<Float> AVAILABLE_MAX_DIGITAL_ZOOM =
- new Key<Float>("android.scaler.availableMaxDigitalZoom", float.class);
- public static final Key<long[]> AVAILABLE_PROCESSED_MIN_DURATIONS =
- new Key<long[]>("android.scaler.availableProcessedMinDurations", long[].class);
- public static final Key<android.hardware.camera2.Size[]> AVAILABLE_PROCESSED_SIZES =
- new Key<android.hardware.camera2.Size[]>("android.scaler.availableProcessedSizes", android.hardware.camera2.Size[].class);
-
- }
-
- public static final class Sensor {
- public static final class Info {
- public static final Key<android.graphics.Rect> ACTIVE_ARRAY_SIZE =
- new Key<android.graphics.Rect>("android.sensor.info.activeArraySize", android.graphics.Rect.class);
- public static final Key<int[]> SENSITIVITY_RANGE =
- new Key<int[]>("android.sensor.info.sensitivityRange", int[].class);
- public static final Key<long[]> EXPOSURE_TIME_RANGE =
- new Key<long[]>("android.sensor.info.exposureTimeRange", long[].class);
- public static final Key<Long> MAX_FRAME_DURATION =
- new Key<Long>("android.sensor.info.maxFrameDuration", long.class);
- public static final Key<android.hardware.camera2.Size> PHYSICAL_SIZE =
- new Key<android.hardware.camera2.Size>("android.sensor.info.physicalSize", android.hardware.camera2.Size.class);
- }
- public static final Key<Rational> BASE_GAIN_FACTOR =
- new Key<Rational>("android.sensor.baseGainFactor", Rational.class);
- public static final Key<Integer> MAX_ANALOG_SENSITIVITY =
- new Key<Integer>("android.sensor.maxAnalogSensitivity", int.class);
- public static final Key<Integer> ORIENTATION =
- new Key<Integer>("android.sensor.orientation", int.class);
-
- }
-
- public static final class Statistics {
- public static final class Info {
- public static final Key<byte[]> AVAILABLE_FACE_DETECT_MODES =
- new Key<byte[]>("android.statistics.info.availableFaceDetectModes", byte[].class);
- public static final Key<Integer> MAX_FACE_COUNT =
- new Key<Integer>("android.statistics.info.maxFaceCount", int.class);
- }
-
- }
-
- public static final class Tonemap {
- public static final Key<Integer> MAX_CURVE_POINTS =
- new Key<Integer>("android.tonemap.maxCurvePoints", int.class);
-
- }
-
- /**
- * @hide
- */
- public static final class Led {
-
- /**
- * @hide
- */
- public static final class AvailableLedsKey extends Key<Led.AvailableLedsKey.Enum[]> {
- public enum Enum {
- TRANSMIT;
- }
-
- public static final Enum TRANSMIT = Enum.TRANSMIT;
-
- // TODO: remove requirement for constructor by making Key an interface
- private AvailableLedsKey(String name) {
- super(name, Led.AvailableLedsKey.Enum[].class);
- }
-
- }
-
- /**
- * @hide
- */
- public static final Key<Led.AvailableLedsKey.Enum[]> AVAILABLE_LEDS =
- new AvailableLedsKey("android.led.availableLeds");
-
- }
-
- public static final class Info {
-
- public static final class SupportedHardwareLevelKey extends Key<Info.SupportedHardwareLevelKey.Enum> {
- public enum Enum {
- LIMITED,
- FULL;
- }
-
- public static final Enum LIMITED = Enum.LIMITED;
- public static final Enum FULL = Enum.FULL;
-
- // TODO: remove requirement for constructor by making Key an interface
- private SupportedHardwareLevelKey(String name) {
- super(name, Info.SupportedHardwareLevelKey.Enum.class);
- }
-
- }
-
- public static final Key<Info.SupportedHardwareLevelKey.Enum> SUPPORTED_HARDWARE_LEVEL =
- new SupportedHardwareLevelKey("android.info.supportedHardwareLevel");
-
- }
-
-}
-
-
diff --git a/core/java/android/hardware/camera2/CaptureRequestKeys.java b/core/java/android/hardware/camera2/CaptureRequestKeys.java
deleted file mode 100644
index 17de8f0..0000000
--- a/core/java/android/hardware/camera2/CaptureRequestKeys.java
+++ /dev/null
@@ -1,617 +0,0 @@
-/*
- * Copyright (C) 2013 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 android.hardware.camera2;
-
-import static android.hardware.camera2.CameraMetadata.Key;
-
-/**
- * ! Do not edit this file directly !
- *
- * Generated automatically from CaptureRequestKeys.mako
- *
- * TODO: Include a hash of the input files here that the build can check.
- */
-
-/**
- * The base class for camera controls and information.
- *
- * This class defines the basic key/value map used for querying for camera
- * characteristics or capture results, and for setting camera request
- * parameters.
- *
- * @see CaptureRequest
- * @see CameraMetadata
- * @hide
- **/
-public final class CaptureRequestKeys {
- public static final class ColorCorrection {
-
- public static final class ModeKey extends Key<ColorCorrection.ModeKey.Enum> {
- public enum Enum {
- TRANSFORM_MATRIX,
- FAST,
- HIGH_QUALITY;
- }
-
- public static final Enum TRANSFORM_MATRIX = Enum.TRANSFORM_MATRIX;
- public static final Enum FAST = Enum.FAST;
- public static final Enum HIGH_QUALITY = Enum.HIGH_QUALITY;
-
- // TODO: remove requirement for constructor by making Key an interface
- private ModeKey(String name) {
- super(name, ColorCorrection.ModeKey.Enum.class);
- }
-
- }
-
- public static final Key<ColorCorrection.ModeKey.Enum> MODE =
- new ModeKey("android.colorCorrection.mode");
- public static final Key<Rational[]> TRANSFORM =
- new Key<Rational[]>("android.colorCorrection.transform", Rational[].class);
- public static final Key<float[]> GAINS =
- new Key<float[]>("android.colorCorrection.gains", float[].class);
-
- }
-
- public static final class Control {
-
- public static final class AeAntibandingModeKey extends Key<Control.AeAntibandingModeKey.Enum> {
- public enum Enum {
- OFF,
- _50HZ,
- _60HZ,
- AUTO;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum _50HZ = Enum._50HZ;
- public static final Enum _60HZ = Enum._60HZ;
- public static final Enum AUTO = Enum.AUTO;
-
- // TODO: remove requirement for constructor by making Key an interface
- private AeAntibandingModeKey(String name) {
- super(name, Control.AeAntibandingModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Control.AeAntibandingModeKey.Enum> AE_ANTIBANDING_MODE =
- new AeAntibandingModeKey("android.control.aeAntibandingMode");
- public static final Key<Integer> AE_EXPOSURE_COMPENSATION =
- new Key<Integer>("android.control.aeExposureCompensation", int.class);
- public static final Key<Boolean> AE_LOCK =
- new Key<Boolean>("android.control.aeLock", boolean.class);
-
- public static final class AeModeKey extends Key<Control.AeModeKey.Enum> {
- public enum Enum {
- OFF,
- ON,
- ON_AUTO_FLASH,
- ON_ALWAYS_FLASH,
- ON_AUTO_FLASH_REDEYE;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum ON = Enum.ON;
- public static final Enum ON_AUTO_FLASH = Enum.ON_AUTO_FLASH;
- public static final Enum ON_ALWAYS_FLASH = Enum.ON_ALWAYS_FLASH;
- public static final Enum ON_AUTO_FLASH_REDEYE = Enum.ON_AUTO_FLASH_REDEYE;
-
- // TODO: remove requirement for constructor by making Key an interface
- private AeModeKey(String name) {
- super(name, Control.AeModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Control.AeModeKey.Enum> AE_MODE =
- new AeModeKey("android.control.aeMode");
- public static final Key<int[]> AE_REGIONS =
- new Key<int[]>("android.control.aeRegions", int[].class);
- public static final Key<int[]> AE_TARGET_FPS_RANGE =
- new Key<int[]>("android.control.aeTargetFpsRange", int[].class);
-
- public static final class AePrecaptureTriggerKey extends Key<Control.AePrecaptureTriggerKey.Enum> {
- public enum Enum {
- IDLE,
- START;
- }
-
- public static final Enum IDLE = Enum.IDLE;
- public static final Enum START = Enum.START;
-
- // TODO: remove requirement for constructor by making Key an interface
- private AePrecaptureTriggerKey(String name) {
- super(name, Control.AePrecaptureTriggerKey.Enum.class);
- }
-
- }
-
- public static final Key<Control.AePrecaptureTriggerKey.Enum> AE_PRECAPTURE_TRIGGER =
- new AePrecaptureTriggerKey("android.control.aePrecaptureTrigger");
-
- public static final class AfModeKey extends Key<Control.AfModeKey.Enum> {
- public enum Enum {
- OFF,
- AUTO,
- MACRO,
- CONTINUOUS_VIDEO,
- CONTINUOUS_PICTURE,
- EDOF;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum AUTO = Enum.AUTO;
- public static final Enum MACRO = Enum.MACRO;
- public static final Enum CONTINUOUS_VIDEO = Enum.CONTINUOUS_VIDEO;
- public static final Enum CONTINUOUS_PICTURE = Enum.CONTINUOUS_PICTURE;
- public static final Enum EDOF = Enum.EDOF;
-
- // TODO: remove requirement for constructor by making Key an interface
- private AfModeKey(String name) {
- super(name, Control.AfModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Control.AfModeKey.Enum> AF_MODE =
- new AfModeKey("android.control.afMode");
- public static final Key<int[]> AF_REGIONS =
- new Key<int[]>("android.control.afRegions", int[].class);
-
- public static final class AfTriggerKey extends Key<Control.AfTriggerKey.Enum> {
- public enum Enum {
- IDLE,
- START,
- CANCEL;
- }
-
- public static final Enum IDLE = Enum.IDLE;
- public static final Enum START = Enum.START;
- public static final Enum CANCEL = Enum.CANCEL;
-
- // TODO: remove requirement for constructor by making Key an interface
- private AfTriggerKey(String name) {
- super(name, Control.AfTriggerKey.Enum.class);
- }
-
- }
-
- public static final Key<Control.AfTriggerKey.Enum> AF_TRIGGER =
- new AfTriggerKey("android.control.afTrigger");
- public static final Key<Boolean> AWB_LOCK =
- new Key<Boolean>("android.control.awbLock", boolean.class);
-
- public static final class AwbModeKey extends Key<Control.AwbModeKey.Enum> {
- public enum Enum {
- OFF,
- AUTO,
- INCANDESCENT,
- FLUORESCENT,
- WARM_FLUORESCENT,
- DAYLIGHT,
- CLOUDY_DAYLIGHT,
- TWILIGHT,
- SHADE;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum AUTO = Enum.AUTO;
- public static final Enum INCANDESCENT = Enum.INCANDESCENT;
- public static final Enum FLUORESCENT = Enum.FLUORESCENT;
- public static final Enum WARM_FLUORESCENT = Enum.WARM_FLUORESCENT;
- public static final Enum DAYLIGHT = Enum.DAYLIGHT;
- public static final Enum CLOUDY_DAYLIGHT = Enum.CLOUDY_DAYLIGHT;
- public static final Enum TWILIGHT = Enum.TWILIGHT;
- public static final Enum SHADE = Enum.SHADE;
-
- // TODO: remove requirement for constructor by making Key an interface
- private AwbModeKey(String name) {
- super(name, Control.AwbModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Control.AwbModeKey.Enum> AWB_MODE =
- new AwbModeKey("android.control.awbMode");
- public static final Key<int[]> AWB_REGIONS =
- new Key<int[]>("android.control.awbRegions", int[].class);
-
- public static final class CaptureIntentKey extends Key<Control.CaptureIntentKey.Enum> {
- public enum Enum {
- CUSTOM,
- PREVIEW,
- STILL_CAPTURE,
- VIDEO_RECORD,
- VIDEO_SNAPSHOT,
- ZERO_SHUTTER_LAG;
- }
-
- public static final Enum CUSTOM = Enum.CUSTOM;
- public static final Enum PREVIEW = Enum.PREVIEW;
- public static final Enum STILL_CAPTURE = Enum.STILL_CAPTURE;
- public static final Enum VIDEO_RECORD = Enum.VIDEO_RECORD;
- public static final Enum VIDEO_SNAPSHOT = Enum.VIDEO_SNAPSHOT;
- public static final Enum ZERO_SHUTTER_LAG = Enum.ZERO_SHUTTER_LAG;
-
- // TODO: remove requirement for constructor by making Key an interface
- private CaptureIntentKey(String name) {
- super(name, Control.CaptureIntentKey.Enum.class);
- }
-
- }
-
- public static final Key<Control.CaptureIntentKey.Enum> CAPTURE_INTENT =
- new CaptureIntentKey("android.control.captureIntent");
-
- public static final class EffectModeKey extends Key<Control.EffectModeKey.Enum> {
- public enum Enum {
- OFF,
- MONO,
- NEGATIVE,
- SOLARIZE,
- SEPIA,
- POSTERIZE,
- WHITEBOARD,
- BLACKBOARD,
- AQUA;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum MONO = Enum.MONO;
- public static final Enum NEGATIVE = Enum.NEGATIVE;
- public static final Enum SOLARIZE = Enum.SOLARIZE;
- public static final Enum SEPIA = Enum.SEPIA;
- public static final Enum POSTERIZE = Enum.POSTERIZE;
- public static final Enum WHITEBOARD = Enum.WHITEBOARD;
- public static final Enum BLACKBOARD = Enum.BLACKBOARD;
- public static final Enum AQUA = Enum.AQUA;
-
- // TODO: remove requirement for constructor by making Key an interface
- private EffectModeKey(String name) {
- super(name, Control.EffectModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Control.EffectModeKey.Enum> EFFECT_MODE =
- new EffectModeKey("android.control.effectMode");
-
- public static final class ModeKey extends Key<Control.ModeKey.Enum> {
- public enum Enum {
- OFF,
- AUTO,
- USE_SCENE_MODE;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum AUTO = Enum.AUTO;
- public static final Enum USE_SCENE_MODE = Enum.USE_SCENE_MODE;
-
- // TODO: remove requirement for constructor by making Key an interface
- private ModeKey(String name) {
- super(name, Control.ModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Control.ModeKey.Enum> MODE =
- new ModeKey("android.control.mode");
-
- public static final class SceneModeKey extends Key<Control.SceneModeKey.Enum> {
- public enum Enum {
- UNSUPPORTED,
- FACE_PRIORITY,
- ACTION,
- PORTRAIT,
- LANDSCAPE,
- NIGHT,
- NIGHT_PORTRAIT,
- THEATRE,
- BEACH,
- SNOW,
- SUNSET,
- STEADYPHOTO,
- FIREWORKS,
- SPORTS,
- PARTY,
- CANDLELIGHT,
- BARCODE;
- }
-
- public static final Enum UNSUPPORTED = Enum.UNSUPPORTED;
- public static final Enum FACE_PRIORITY = Enum.FACE_PRIORITY;
- public static final Enum ACTION = Enum.ACTION;
- public static final Enum PORTRAIT = Enum.PORTRAIT;
- public static final Enum LANDSCAPE = Enum.LANDSCAPE;
- public static final Enum NIGHT = Enum.NIGHT;
- public static final Enum NIGHT_PORTRAIT = Enum.NIGHT_PORTRAIT;
- public static final Enum THEATRE = Enum.THEATRE;
- public static final Enum BEACH = Enum.BEACH;
- public static final Enum SNOW = Enum.SNOW;
- public static final Enum SUNSET = Enum.SUNSET;
- public static final Enum STEADYPHOTO = Enum.STEADYPHOTO;
- public static final Enum FIREWORKS = Enum.FIREWORKS;
- public static final Enum SPORTS = Enum.SPORTS;
- public static final Enum PARTY = Enum.PARTY;
- public static final Enum CANDLELIGHT = Enum.CANDLELIGHT;
- public static final Enum BARCODE = Enum.BARCODE;
-
- // TODO: remove requirement for constructor by making Key an interface
- private SceneModeKey(String name) {
- super(name, Control.SceneModeKey.Enum.class);
- }
-
- static {
- CameraMetadata.registerEnumValues(Control.SceneModeKey.Enum.class, new int[] {
- 0, // UNSUPPORTED
- 1, // FACE_PRIORITY
- 2, // ACTION
- 3, // PORTRAIT
- 4, // LANDSCAPE
- 5, // NIGHT
- 6, // NIGHT_PORTRAIT
- 7, // THEATRE
- 8, // BEACH
- 9, // SNOW
- 10, // SUNSET
- 11, // STEADYPHOTO
- 12, // FIREWORKS
- 13, // SPORTS
- 14, // PARTY
- 15, // CANDLELIGHT
- 16 // BARCODE
- });
- }
- }
-
- public static final Key<Control.SceneModeKey.Enum> SCENE_MODE =
- new SceneModeKey("android.control.sceneMode");
- public static final Key<Boolean> VIDEO_STABILIZATION_MODE =
- new Key<Boolean>("android.control.videoStabilizationMode", boolean.class);
-
- }
-
- public static final class Edge {
-
- public static final class ModeKey extends Key<Edge.ModeKey.Enum> {
- public enum Enum {
- OFF,
- FAST,
- HIGH_QUALITY;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum FAST = Enum.FAST;
- public static final Enum HIGH_QUALITY = Enum.HIGH_QUALITY;
-
- // TODO: remove requirement for constructor by making Key an interface
- private ModeKey(String name) {
- super(name, Edge.ModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Edge.ModeKey.Enum> MODE =
- new ModeKey("android.edge.mode");
-
- }
-
- public static final class Flash {
-
- public static final class ModeKey extends Key<Flash.ModeKey.Enum> {
- public enum Enum {
- OFF,
- SINGLE,
- TORCH;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum SINGLE = Enum.SINGLE;
- public static final Enum TORCH = Enum.TORCH;
-
- // TODO: remove requirement for constructor by making Key an interface
- private ModeKey(String name) {
- super(name, Flash.ModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Flash.ModeKey.Enum> MODE =
- new ModeKey("android.flash.mode");
-
- }
-
- public static final class Jpeg {
- public static final Key<double[]> GPS_COORDINATES =
- new Key<double[]>("android.jpeg.gpsCoordinates", double[].class);
- public static final Key<String> GPS_PROCESSING_METHOD =
- new Key<String>("android.jpeg.gpsProcessingMethod", String.class);
- public static final Key<Long> GPS_TIMESTAMP =
- new Key<Long>("android.jpeg.gpsTimestamp", long.class);
- public static final Key<Integer> ORIENTATION =
- new Key<Integer>("android.jpeg.orientation", int.class);
- public static final Key<Byte> QUALITY =
- new Key<Byte>("android.jpeg.quality", byte.class);
- public static final Key<Byte> THUMBNAIL_QUALITY =
- new Key<Byte>("android.jpeg.thumbnailQuality", byte.class);
- public static final Key<android.hardware.camera2.Size> THUMBNAIL_SIZE =
- new Key<android.hardware.camera2.Size>("android.jpeg.thumbnailSize", android.hardware.camera2.Size.class);
-
- }
-
- public static final class Lens {
- public static final Key<Float> APERTURE =
- new Key<Float>("android.lens.aperture", float.class);
- public static final Key<Float> FILTER_DENSITY =
- new Key<Float>("android.lens.filterDensity", float.class);
- public static final Key<Float> FOCAL_LENGTH =
- new Key<Float>("android.lens.focalLength", float.class);
- public static final Key<Float> FOCUS_DISTANCE =
- new Key<Float>("android.lens.focusDistance", float.class);
-
- public static final class OpticalStabilizationModeKey extends Key<Lens.OpticalStabilizationModeKey.Enum> {
- public enum Enum {
- OFF,
- ON;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum ON = Enum.ON;
-
- // TODO: remove requirement for constructor by making Key an interface
- private OpticalStabilizationModeKey(String name) {
- super(name, Lens.OpticalStabilizationModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Lens.OpticalStabilizationModeKey.Enum> OPTICAL_STABILIZATION_MODE =
- new OpticalStabilizationModeKey("android.lens.opticalStabilizationMode");
-
- }
-
- public static final class NoiseReduction {
-
- public static final class ModeKey extends Key<NoiseReduction.ModeKey.Enum> {
- public enum Enum {
- OFF,
- FAST,
- HIGH_QUALITY;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum FAST = Enum.FAST;
- public static final Enum HIGH_QUALITY = Enum.HIGH_QUALITY;
-
- // TODO: remove requirement for constructor by making Key an interface
- private ModeKey(String name) {
- super(name, NoiseReduction.ModeKey.Enum.class);
- }
-
- }
-
- public static final Key<NoiseReduction.ModeKey.Enum> MODE =
- new ModeKey("android.noiseReduction.mode");
-
- }
-
- /**
- * @hide
- */
- public static final class Request {
- /**
- * @hide
- */
- public static final Key<Integer> ID =
- new Key<Integer>("android.request.id", int.class);
-
- }
-
- public static final class Scaler {
- public static final Key<android.graphics.Rect> CROP_REGION =
- new Key<android.graphics.Rect>("android.scaler.cropRegion", android.graphics.Rect.class);
-
- }
-
- public static final class Sensor {
- public static final Key<Long> EXPOSURE_TIME =
- new Key<Long>("android.sensor.exposureTime", long.class);
- public static final Key<Long> FRAME_DURATION =
- new Key<Long>("android.sensor.frameDuration", long.class);
- public static final Key<Integer> SENSITIVITY =
- new Key<Integer>("android.sensor.sensitivity", int.class);
-
- }
-
- public static final class Statistics {
-
- public static final class FaceDetectModeKey extends Key<Statistics.FaceDetectModeKey.Enum> {
- public enum Enum {
- OFF,
- SIMPLE,
- FULL;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum SIMPLE = Enum.SIMPLE;
- public static final Enum FULL = Enum.FULL;
-
- // TODO: remove requirement for constructor by making Key an interface
- private FaceDetectModeKey(String name) {
- super(name, Statistics.FaceDetectModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Statistics.FaceDetectModeKey.Enum> FACE_DETECT_MODE =
- new FaceDetectModeKey("android.statistics.faceDetectMode");
-
- }
-
- public static final class Tonemap {
- public static final Key<Float> CURVE_BLUE =
- new Key<Float>("android.tonemap.curveBlue", float.class);
- public static final Key<Float> CURVE_GREEN =
- new Key<Float>("android.tonemap.curveGreen", float.class);
- public static final Key<float[]> CURVE_RED =
- new Key<float[]>("android.tonemap.curveRed", float[].class);
-
- public static final class ModeKey extends Key<Tonemap.ModeKey.Enum> {
- public enum Enum {
- CONTRAST_CURVE,
- FAST,
- HIGH_QUALITY;
- }
-
- public static final Enum CONTRAST_CURVE = Enum.CONTRAST_CURVE;
- public static final Enum FAST = Enum.FAST;
- public static final Enum HIGH_QUALITY = Enum.HIGH_QUALITY;
-
- // TODO: remove requirement for constructor by making Key an interface
- private ModeKey(String name) {
- super(name, Tonemap.ModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Tonemap.ModeKey.Enum> MODE =
- new ModeKey("android.tonemap.mode");
-
- }
-
- /**
- * @hide
- */
- public static final class Led {
- /**
- * @hide
- */
- public static final Key<Boolean> TRANSMIT =
- new Key<Boolean>("android.led.transmit", boolean.class);
-
- }
-
- public static final class BlackLevel {
- public static final Key<Boolean> LOCK =
- new Key<Boolean>("android.blackLevel.lock", boolean.class);
-
- }
-
-}
-
-
diff --git a/core/java/android/hardware/camera2/CaptureResultKeys.java b/core/java/android/hardware/camera2/CaptureResultKeys.java
deleted file mode 100644
index dd3ed83..0000000
--- a/core/java/android/hardware/camera2/CaptureResultKeys.java
+++ /dev/null
@@ -1,533 +0,0 @@
-/*
- * Copyright (C) 2013 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 android.hardware.camera2;
-
-import static android.hardware.camera2.CameraMetadata.Key;
-
-/**
- * ! Do not edit this file directly !
- *
- * Generated automatically from CaptureResultKeys.mako
- *
- * TODO: Include a hash of the input files here that the build can check.
- */
-
-/**
- * The base class for camera controls and information.
- *
- * This class defines the basic key/value map used for querying for camera
- * characteristics or capture results, and for setting camera request
- * parameters.
- *
- * @see CaptureResult
- * @see CameraMetadata
- * @hide
- **/
-public final class CaptureResultKeys {
- public static final class ColorCorrection {
- public static final Key<Rational[]> TRANSFORM =
- new Key<Rational[]>("android.colorCorrection.transform", Rational[].class);
- public static final Key<float[]> GAINS =
- new Key<float[]>("android.colorCorrection.gains", float[].class);
-
- }
-
- public static final class Control {
- /**
- * @hide
- */
- public static final Key<Integer> AE_PRECAPTURE_ID =
- new Key<Integer>("android.control.aePrecaptureId", int.class);
- public static final Key<int[]> AE_REGIONS =
- new Key<int[]>("android.control.aeRegions", int[].class);
-
- public static final class AeStateKey extends Key<Control.AeStateKey.Enum> {
- public enum Enum {
- INACTIVE,
- SEARCHING,
- CONVERGED,
- LOCKED,
- FLASH_REQUIRED,
- PRECAPTURE;
- }
-
- public static final Enum INACTIVE = Enum.INACTIVE;
- public static final Enum SEARCHING = Enum.SEARCHING;
- public static final Enum CONVERGED = Enum.CONVERGED;
- public static final Enum LOCKED = Enum.LOCKED;
- public static final Enum FLASH_REQUIRED = Enum.FLASH_REQUIRED;
- public static final Enum PRECAPTURE = Enum.PRECAPTURE;
-
- // TODO: remove requirement for constructor by making Key an interface
- private AeStateKey(String name) {
- super(name, Control.AeStateKey.Enum.class);
- }
-
- }
-
- public static final Key<Control.AeStateKey.Enum> AE_STATE =
- new AeStateKey("android.control.aeState");
-
- public static final class AfModeKey extends Key<Control.AfModeKey.Enum> {
- public enum Enum {
- OFF,
- AUTO,
- MACRO,
- CONTINUOUS_VIDEO,
- CONTINUOUS_PICTURE,
- EDOF;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum AUTO = Enum.AUTO;
- public static final Enum MACRO = Enum.MACRO;
- public static final Enum CONTINUOUS_VIDEO = Enum.CONTINUOUS_VIDEO;
- public static final Enum CONTINUOUS_PICTURE = Enum.CONTINUOUS_PICTURE;
- public static final Enum EDOF = Enum.EDOF;
-
- // TODO: remove requirement for constructor by making Key an interface
- private AfModeKey(String name) {
- super(name, Control.AfModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Control.AfModeKey.Enum> AF_MODE =
- new AfModeKey("android.control.afMode");
- public static final Key<int[]> AF_REGIONS =
- new Key<int[]>("android.control.afRegions", int[].class);
-
- public static final class AfStateKey extends Key<Control.AfStateKey.Enum> {
- public enum Enum {
- INACTIVE,
- PASSIVE_SCAN,
- PASSIVE_FOCUSED,
- ACTIVE_SCAN,
- FOCUSED_LOCKED,
- NOT_FOCUSED_LOCKED;
- }
-
- public static final Enum INACTIVE = Enum.INACTIVE;
- public static final Enum PASSIVE_SCAN = Enum.PASSIVE_SCAN;
- public static final Enum PASSIVE_FOCUSED = Enum.PASSIVE_FOCUSED;
- public static final Enum ACTIVE_SCAN = Enum.ACTIVE_SCAN;
- public static final Enum FOCUSED_LOCKED = Enum.FOCUSED_LOCKED;
- public static final Enum NOT_FOCUSED_LOCKED = Enum.NOT_FOCUSED_LOCKED;
-
- // TODO: remove requirement for constructor by making Key an interface
- private AfStateKey(String name) {
- super(name, Control.AfStateKey.Enum.class);
- }
-
- }
-
- public static final Key<Control.AfStateKey.Enum> AF_STATE =
- new AfStateKey("android.control.afState");
- /**
- * @hide
- */
- public static final Key<Integer> AF_TRIGGER_ID =
- new Key<Integer>("android.control.afTriggerId", int.class);
-
- public static final class AwbModeKey extends Key<Control.AwbModeKey.Enum> {
- public enum Enum {
- OFF,
- AUTO,
- INCANDESCENT,
- FLUORESCENT,
- WARM_FLUORESCENT,
- DAYLIGHT,
- CLOUDY_DAYLIGHT,
- TWILIGHT,
- SHADE;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum AUTO = Enum.AUTO;
- public static final Enum INCANDESCENT = Enum.INCANDESCENT;
- public static final Enum FLUORESCENT = Enum.FLUORESCENT;
- public static final Enum WARM_FLUORESCENT = Enum.WARM_FLUORESCENT;
- public static final Enum DAYLIGHT = Enum.DAYLIGHT;
- public static final Enum CLOUDY_DAYLIGHT = Enum.CLOUDY_DAYLIGHT;
- public static final Enum TWILIGHT = Enum.TWILIGHT;
- public static final Enum SHADE = Enum.SHADE;
-
- // TODO: remove requirement for constructor by making Key an interface
- private AwbModeKey(String name) {
- super(name, Control.AwbModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Control.AwbModeKey.Enum> AWB_MODE =
- new AwbModeKey("android.control.awbMode");
- public static final Key<int[]> AWB_REGIONS =
- new Key<int[]>("android.control.awbRegions", int[].class);
-
- public static final class AwbStateKey extends Key<Control.AwbStateKey.Enum> {
- public enum Enum {
- INACTIVE,
- SEARCHING,
- CONVERGED,
- LOCKED;
- }
-
- public static final Enum INACTIVE = Enum.INACTIVE;
- public static final Enum SEARCHING = Enum.SEARCHING;
- public static final Enum CONVERGED = Enum.CONVERGED;
- public static final Enum LOCKED = Enum.LOCKED;
-
- // TODO: remove requirement for constructor by making Key an interface
- private AwbStateKey(String name) {
- super(name, Control.AwbStateKey.Enum.class);
- }
-
- }
-
- public static final Key<Control.AwbStateKey.Enum> AWB_STATE =
- new AwbStateKey("android.control.awbState");
-
- public static final class ModeKey extends Key<Control.ModeKey.Enum> {
- public enum Enum {
- OFF,
- AUTO,
- USE_SCENE_MODE;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum AUTO = Enum.AUTO;
- public static final Enum USE_SCENE_MODE = Enum.USE_SCENE_MODE;
-
- // TODO: remove requirement for constructor by making Key an interface
- private ModeKey(String name) {
- super(name, Control.ModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Control.ModeKey.Enum> MODE =
- new ModeKey("android.control.mode");
-
- }
-
- public static final class Edge {
-
- public static final class ModeKey extends Key<Edge.ModeKey.Enum> {
- public enum Enum {
- OFF,
- FAST,
- HIGH_QUALITY;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum FAST = Enum.FAST;
- public static final Enum HIGH_QUALITY = Enum.HIGH_QUALITY;
-
- // TODO: remove requirement for constructor by making Key an interface
- private ModeKey(String name) {
- super(name, Edge.ModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Edge.ModeKey.Enum> MODE =
- new ModeKey("android.edge.mode");
-
- }
-
- public static final class Flash {
-
- public static final class ModeKey extends Key<Flash.ModeKey.Enum> {
- public enum Enum {
- OFF,
- SINGLE,
- TORCH;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum SINGLE = Enum.SINGLE;
- public static final Enum TORCH = Enum.TORCH;
-
- // TODO: remove requirement for constructor by making Key an interface
- private ModeKey(String name) {
- super(name, Flash.ModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Flash.ModeKey.Enum> MODE =
- new ModeKey("android.flash.mode");
-
- public static final class StateKey extends Key<Flash.StateKey.Enum> {
- public enum Enum {
- UNAVAILABLE,
- CHARGING,
- READY,
- FIRED;
- }
-
- public static final Enum UNAVAILABLE = Enum.UNAVAILABLE;
- public static final Enum CHARGING = Enum.CHARGING;
- public static final Enum READY = Enum.READY;
- public static final Enum FIRED = Enum.FIRED;
-
- // TODO: remove requirement for constructor by making Key an interface
- private StateKey(String name) {
- super(name, Flash.StateKey.Enum.class);
- }
-
- }
-
- public static final Key<Flash.StateKey.Enum> STATE =
- new StateKey("android.flash.state");
-
- }
-
- public static final class Jpeg {
- public static final Key<double[]> GPS_COORDINATES =
- new Key<double[]>("android.jpeg.gpsCoordinates", double[].class);
- public static final Key<String> GPS_PROCESSING_METHOD =
- new Key<String>("android.jpeg.gpsProcessingMethod", String.class);
- public static final Key<Long> GPS_TIMESTAMP =
- new Key<Long>("android.jpeg.gpsTimestamp", long.class);
- public static final Key<Integer> ORIENTATION =
- new Key<Integer>("android.jpeg.orientation", int.class);
- public static final Key<Byte> QUALITY =
- new Key<Byte>("android.jpeg.quality", byte.class);
- public static final Key<Byte> THUMBNAIL_QUALITY =
- new Key<Byte>("android.jpeg.thumbnailQuality", byte.class);
- public static final Key<android.hardware.camera2.Size> THUMBNAIL_SIZE =
- new Key<android.hardware.camera2.Size>("android.jpeg.thumbnailSize", android.hardware.camera2.Size.class);
-
- }
-
- public static final class Lens {
- public static final Key<Float> APERTURE =
- new Key<Float>("android.lens.aperture", float.class);
- public static final Key<Float> FILTER_DENSITY =
- new Key<Float>("android.lens.filterDensity", float.class);
- public static final Key<Float> FOCAL_LENGTH =
- new Key<Float>("android.lens.focalLength", float.class);
- public static final Key<Float> FOCUS_DISTANCE =
- new Key<Float>("android.lens.focusDistance", float.class);
- public static final Key<Float> FOCUS_RANGE =
- new Key<Float>("android.lens.focusRange", float.class);
-
- public static final class OpticalStabilizationModeKey extends Key<Lens.OpticalStabilizationModeKey.Enum> {
- public enum Enum {
- OFF,
- ON;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum ON = Enum.ON;
-
- // TODO: remove requirement for constructor by making Key an interface
- private OpticalStabilizationModeKey(String name) {
- super(name, Lens.OpticalStabilizationModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Lens.OpticalStabilizationModeKey.Enum> OPTICAL_STABILIZATION_MODE =
- new OpticalStabilizationModeKey("android.lens.opticalStabilizationMode");
-
- public static final class StateKey extends Key<Lens.StateKey.Enum> {
- public enum Enum {
- STATIONARY;
- }
-
- public static final Enum STATIONARY = Enum.STATIONARY;
-
- // TODO: remove requirement for constructor by making Key an interface
- private StateKey(String name) {
- super(name, Lens.StateKey.Enum.class);
- }
-
- }
-
- public static final Key<Lens.StateKey.Enum> STATE =
- new StateKey("android.lens.state");
-
- }
-
- public static final class NoiseReduction {
-
- public static final class ModeKey extends Key<NoiseReduction.ModeKey.Enum> {
- public enum Enum {
- OFF,
- FAST,
- HIGH_QUALITY;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum FAST = Enum.FAST;
- public static final Enum HIGH_QUALITY = Enum.HIGH_QUALITY;
-
- // TODO: remove requirement for constructor by making Key an interface
- private ModeKey(String name) {
- super(name, NoiseReduction.ModeKey.Enum.class);
- }
-
- }
-
- public static final Key<NoiseReduction.ModeKey.Enum> MODE =
- new ModeKey("android.noiseReduction.mode");
-
- }
-
- public static final class Request {
- public static final Key<Integer> FRAME_COUNT =
- new Key<Integer>("android.request.frameCount", int.class);
- /**
- * @hide
- */
- public static final Key<Integer> ID =
- new Key<Integer>("android.request.id", int.class);
-
- }
-
- public static final class Scaler {
- public static final Key<android.graphics.Rect> CROP_REGION =
- new Key<android.graphics.Rect>("android.scaler.cropRegion", android.graphics.Rect.class);
-
- }
-
- public static final class Sensor {
- public static final Key<Long> EXPOSURE_TIME =
- new Key<Long>("android.sensor.exposureTime", long.class);
- public static final Key<Long> FRAME_DURATION =
- new Key<Long>("android.sensor.frameDuration", long.class);
- public static final Key<Integer> SENSITIVITY =
- new Key<Integer>("android.sensor.sensitivity", int.class);
- public static final Key<Long> TIMESTAMP =
- new Key<Long>("android.sensor.timestamp", long.class);
-
- }
-
- public static final class Statistics {
-
- public static final class FaceDetectModeKey extends Key<Statistics.FaceDetectModeKey.Enum> {
- public enum Enum {
- OFF,
- SIMPLE,
- FULL;
- }
-
- public static final Enum OFF = Enum.OFF;
- public static final Enum SIMPLE = Enum.SIMPLE;
- public static final Enum FULL = Enum.FULL;
-
- // TODO: remove requirement for constructor by making Key an interface
- private FaceDetectModeKey(String name) {
- super(name, Statistics.FaceDetectModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Statistics.FaceDetectModeKey.Enum> FACE_DETECT_MODE =
- new FaceDetectModeKey("android.statistics.faceDetectMode");
- public static final Key<int[]> FACE_IDS =
- new Key<int[]>("android.statistics.faceIds", int[].class);
- public static final Key<int[]> FACE_LANDMARKS =
- new Key<int[]>("android.statistics.faceLandmarks", int[].class);
- public static final Key<android.graphics.Rect[]> FACE_RECTANGLES =
- new Key<android.graphics.Rect[]>("android.statistics.faceRectangles", android.graphics.Rect[].class);
- public static final Key<byte[]> FACE_SCORES =
- new Key<byte[]>("android.statistics.faceScores", byte[].class);
- public static final Key<float[]> LENS_SHADING_MAP =
- new Key<float[]>("android.statistics.lensShadingMap", float[].class);
- public static final Key<float[]> PREDICTED_COLOR_GAINS =
- new Key<float[]>("android.statistics.predictedColorGains", float[].class);
- public static final Key<Rational[]> PREDICTED_COLOR_TRANSFORM =
- new Key<Rational[]>("android.statistics.predictedColorTransform", Rational[].class);
-
- public static final class SceneFlickerKey extends Key<Statistics.SceneFlickerKey.Enum> {
- public enum Enum {
- NONE,
- _50HZ,
- _60HZ;
- }
-
- public static final Enum NONE = Enum.NONE;
- public static final Enum _50HZ = Enum._50HZ;
- public static final Enum _60HZ = Enum._60HZ;
-
- // TODO: remove requirement for constructor by making Key an interface
- private SceneFlickerKey(String name) {
- super(name, Statistics.SceneFlickerKey.Enum.class);
- }
-
- }
-
- public static final Key<Statistics.SceneFlickerKey.Enum> SCENE_FLICKER =
- new SceneFlickerKey("android.statistics.sceneFlicker");
-
- }
-
- public static final class Tonemap {
- public static final Key<Float> CURVE_BLUE =
- new Key<Float>("android.tonemap.curveBlue", float.class);
- public static final Key<Float> CURVE_GREEN =
- new Key<Float>("android.tonemap.curveGreen", float.class);
- public static final Key<float[]> CURVE_RED =
- new Key<float[]>("android.tonemap.curveRed", float[].class);
-
- public static final class ModeKey extends Key<Tonemap.ModeKey.Enum> {
- public enum Enum {
- CONTRAST_CURVE,
- FAST,
- HIGH_QUALITY;
- }
-
- public static final Enum CONTRAST_CURVE = Enum.CONTRAST_CURVE;
- public static final Enum FAST = Enum.FAST;
- public static final Enum HIGH_QUALITY = Enum.HIGH_QUALITY;
-
- // TODO: remove requirement for constructor by making Key an interface
- private ModeKey(String name) {
- super(name, Tonemap.ModeKey.Enum.class);
- }
-
- }
-
- public static final Key<Tonemap.ModeKey.Enum> MODE =
- new ModeKey("android.tonemap.mode");
-
- }
-
- /**
- * @hide
- */
- public static final class Led {
- /**
- * @hide
- */
- public static final Key<Boolean> TRANSMIT =
- new Key<Boolean>("android.led.transmit", boolean.class);
-
- }
-
- public static final class BlackLevel {
- public static final Key<Boolean> LOCK =
- new Key<Boolean>("android.blackLevel.lock", boolean.class);
-
- }
-
-}
-
-
diff --git a/core/java/android/net/PacProxySelector.java b/core/java/android/net/PacProxySelector.java
index d3ce2e5..b674324 100644
--- a/core/java/android/net/PacProxySelector.java
+++ b/core/java/android/net/PacProxySelector.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2013 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 android.net;
@@ -25,19 +40,25 @@
private static final String TAG = "PacProxySelector";
public static final String PROXY_SERVICE = "com.android.net.IProxyService";
private IProxyService mProxyService;
+ private final List<Proxy> mDefaultList;
public PacProxySelector() {
mProxyService = IProxyService.Stub.asInterface(
ServiceManager.getService(PROXY_SERVICE));
if (mProxyService == null) {
// Added because of b10267814 where mako is restarting.
- Log.e(TAG, "PackManager: no proxy service");
+ Log.e(TAG, "PacManager: no proxy service");
}
+ mDefaultList = Lists.newArrayList(java.net.Proxy.NO_PROXY);
}
@Override
public List<Proxy> select(URI uri) {
if (mProxyService == null) {
+ mProxyService = IProxyService.Stub.asInterface(
+ ServiceManager.getService(PROXY_SERVICE));
+ }
+ if (mProxyService == null) {
Log.e(TAG, "select: no proxy service return NO_PROXY");
return Lists.newArrayList(java.net.Proxy.NO_PROXY);
}
@@ -53,6 +74,9 @@
} catch (RemoteException e) {
e.printStackTrace();
}
+ if (response == null) {
+ return mDefaultList;
+ }
return parseResponse(response);
}
diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java
index 5b38f57..c3e1438 100644
--- a/core/java/android/net/Proxy.java
+++ b/core/java/android/net/Proxy.java
@@ -48,7 +48,6 @@
private static final String TAG = "Proxy";
private static final ProxySelector sDefaultProxySelector;
- private static PacProxySelector sPacProxySelector;
/**
* Used to notify an app that's caching the default connection proxy
@@ -352,11 +351,8 @@
System.clearProperty("http.nonProxyHosts");
System.clearProperty("https.nonProxyHosts");
}
- if ((pacFileUrl != null) && !TextUtils.isEmpty(pacFileUrl)) {
- if (sPacProxySelector == null) {
- sPacProxySelector = new PacProxySelector();
- }
- ProxySelector.setDefault(sPacProxySelector);
+ if (!TextUtils.isEmpty(pacFileUrl)) {
+ ProxySelector.setDefault(new PacProxySelector());
} else {
ProxySelector.setDefault(sDefaultProxySelector);
}
diff --git a/core/java/android/preference/OnDependencyChangeListener.java b/core/java/android/preference/OnDependencyChangeListener.java
deleted file mode 100644
index ce25e34..0000000
--- a/core/java/android/preference/OnDependencyChangeListener.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2007 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 android.preference;
-
-/**
- * Interface definition for a callback to be invoked when this
- * {@link Preference} changes with respect to enabling/disabling
- * dependents.
- */
-interface OnDependencyChangeListener {
- /**
- * Called when this preference has changed in a way that dependents should
- * care to change their state.
- *
- * @param disablesDependent Whether the dependent should be disabled.
- */
- void onDependencyChanged(Preference dependency, boolean disablesDependent);
-}
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
index 6c02965..08e3d7b 100644
--- a/core/java/android/preference/Preference.java
+++ b/core/java/android/preference/Preference.java
@@ -77,7 +77,7 @@
* @attr ref android.R.styleable#Preference_defaultValue
* @attr ref android.R.styleable#Preference_shouldDisableView
*/
-public class Preference implements Comparable<Preference>, OnDependencyChangeListener {
+public class Preference implements Comparable<Preference> {
/**
* Specify for {@link #setOrder(int)} if a specific order is not required.
*/
@@ -115,6 +115,7 @@
private String mDependencyKey;
private Object mDefaultValue;
private boolean mDependencyMet = true;
+ private boolean mParentDependencyMet = true;
/**
* @see #setShouldDisableView(boolean)
@@ -733,7 +734,7 @@
* @return True if this Preference is enabled, false otherwise.
*/
public boolean isEnabled() {
- return mEnabled && mDependencyMet;
+ return mEnabled && mDependencyMet && mParentDependencyMet;
}
/**
@@ -1259,7 +1260,24 @@
notifyChanged();
}
}
-
+
+ /**
+ * Called when the implicit parent dependency changes.
+ *
+ * @param parent The Preference that this Preference depends on.
+ * @param disableChild Set true to disable this Preference.
+ */
+ public void onParentChanged(Preference parent, boolean disableChild) {
+ if (mParentDependencyMet == disableChild) {
+ mParentDependencyMet = !disableChild;
+
+ // Enabled state can change dependent preferences' states, so notify
+ notifyDependencyChange(shouldDisableDependents());
+
+ notifyChanged();
+ }
+ }
+
/**
* Checks whether this preference's dependents should currently be
* disabled.
diff --git a/core/java/android/preference/PreferenceCategory.java b/core/java/android/preference/PreferenceCategory.java
index d8af3248..229a96a 100644
--- a/core/java/android/preference/PreferenceCategory.java
+++ b/core/java/android/preference/PreferenceCategory.java
@@ -62,4 +62,8 @@
return false;
}
+ @Override
+ public boolean shouldDisableDependents() {
+ return !super.isEnabled();
+ }
}
diff --git a/core/java/android/preference/PreferenceGroup.java b/core/java/android/preference/PreferenceGroup.java
index f33a6be..a5e05ba 100644
--- a/core/java/android/preference/PreferenceGroup.java
+++ b/core/java/android/preference/PreferenceGroup.java
@@ -290,13 +290,14 @@
}
@Override
- public void setEnabled(boolean enabled) {
- super.setEnabled(enabled);
-
- // Dispatch to all contained preferences
+ public void notifyDependencyChange(boolean disableDependents) {
+ super.notifyDependencyChange(disableDependents);
+
+ // Child preferences have an implicit dependency on their containing
+ // group. Dispatch dependency change to all contained preferences.
final int preferenceCount = getPreferenceCount();
for (int i = 0; i < preferenceCount; i++) {
- getPreference(i).setEnabled(enabled);
+ getPreference(i).onParentChanged(this, disableDependents);
}
}
diff --git a/core/java/android/print/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl
index 8178180..5c8a22a 100644
--- a/core/java/android/print/IPrintSpooler.aidl
+++ b/core/java/android/print/IPrintSpooler.aidl
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.os.ParcelFileDescriptor;
+import android.print.PrinterId;
import android.print.IPrintDocumentAdapter;
import android.print.IPrintClient;
import android.print.IPrintSpoolerClient;
@@ -40,10 +41,15 @@
void createPrintJob(String printJobName, in IPrintClient client,
in IPrintDocumentAdapter printAdapter, in PrintAttributes attributes,
IPrintSpoolerCallbacks callback, int appId, int sequence);
- void setPrintJobState(int printJobId, int status, CharSequence error,
+ void setPrintJobState(int printJobId, int status, String error,
IPrintSpoolerCallbacks callback, int sequence);
void setPrintJobTag(int printJobId, String tag, IPrintSpoolerCallbacks callback,
int sequence);
void writePrintJobData(in ParcelFileDescriptor fd, int printJobId);
void setClient(IPrintSpoolerClient client);
+
+ // Printer discovery APIs
+ void onPrintersAdded(in List<PrinterInfo> printers);
+ void onPrintersRemoved(in List<PrinterId> printerIds);
+ void onPrintersUpdated(in List<PrinterInfo> printerIds);
}
diff --git a/core/java/android/print/IPrintSpoolerClient.aidl b/core/java/android/print/IPrintSpoolerClient.aidl
index 8db2169..da60120 100644
--- a/core/java/android/print/IPrintSpoolerClient.aidl
+++ b/core/java/android/print/IPrintSpoolerClient.aidl
@@ -17,7 +17,6 @@
package android.print;
import android.content.ComponentName;
-import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrinterId;
import android.print.PrintJobInfo;
@@ -28,8 +27,14 @@
* @hide
*/
oneway interface IPrintSpoolerClient {
- void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer);
void onPrintJobQueued(in PrintJobInfo printJob);
void onAllPrintJobsForServiceHandled(in ComponentName printService);
void onAllPrintJobsHandled();
+
+ // Printer discovery APIs
+ void createPrinterDiscoverySession();
+ void startPrinterDiscovery(in List<PrinterId> priorityList);
+ void stopPrinterDiscovery();
+ void requestPrinterUpdate(in PrinterId printerId);
+ void destroyPrinterDiscoverySession();
}
diff --git a/core/java/android/print/IPrinterDiscoverySessionController.aidl b/core/java/android/print/IPrinterDiscoverySessionController.aidl
deleted file mode 100644
index 13116ef..0000000
--- a/core/java/android/print/IPrinterDiscoverySessionController.aidl
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2013 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 android.print;
-
-import android.print.PrinterId;
-
-/**
-* Interface for the controlling part of a printer discovery session.
- *
- * @hide
- */
-oneway interface IPrinterDiscoverySessionController {
- void open(in List<PrinterId> priorityList);
- void requestPrinterUpdate(in PrinterId printerId);
- void close();
-}
diff --git a/core/java/android/print/IPrinterDiscoverySessionObserver.aidl b/core/java/android/print/IPrinterDiscoverySessionObserver.aidl
deleted file mode 100644
index a78924c..0000000
--- a/core/java/android/print/IPrinterDiscoverySessionObserver.aidl
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2013 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 android.print;
-
-import android.print.IPrinterDiscoverySessionController;
-import android.print.PrinterId;
-import android.print.PrinterInfo;
-
-/**
- * Interface for the observing part of a printer discovery session.
- *
- * @hide
- */
-oneway interface IPrinterDiscoverySessionObserver {
- void setController(IPrinterDiscoverySessionController controller);
- void onPrintersAdded(in List<PrinterInfo> printers);
- void onPrintersRemoved(in List<PrinterId> printerIds);
- void onPrintersUpdated(in List<PrinterInfo> printerIds);
-}
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 2fb4751..602f3c1 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -104,7 +104,7 @@
private int mId;
/** The human readable print job label. */
- private CharSequence mLabel;
+ private String mLabel;
/** The unique id of the printer. */
private PrinterId mPrinterId;
@@ -128,7 +128,7 @@
private int mCopies;
/** Failure reason if this job failed. */
- private CharSequence mFailureReason;
+ private String mFailureReason;
/** The pages to print */
private PageRange[] mPageRanges;
@@ -163,7 +163,7 @@
private PrintJobInfo(Parcel parcel) {
mId = parcel.readInt();
- mLabel = parcel.readCharSequence();
+ mLabel = parcel.readString();
mPrinterId = parcel.readParcelable(null);
mPrinterName = parcel.readString();
mState = parcel.readInt();
@@ -171,9 +171,7 @@
mUserId = parcel.readInt();
mTag = parcel.readString();
mCopies = parcel.readInt();
- if (parcel.readInt() == 1) {
- mFailureReason = parcel.readCharSequence();
- }
+ mFailureReason = parcel.readString();
if (parcel.readInt() == 1) {
Parcelable[] parcelables = parcel.readParcelableArray(null);
mPageRanges = new PageRange[parcelables.length];
@@ -214,7 +212,7 @@
*
* @return The label.
*/
- public CharSequence getLabel() {
+ public String getLabel() {
return mLabel;
}
@@ -225,7 +223,7 @@
*
* @hide
*/
- public void setLabel(CharSequence label) {
+ public void setLabel(String label) {
mLabel = label;
}
@@ -385,7 +383,7 @@
*
* @hide
*/
- public CharSequence getFailureReason() {
+ public String getFailureReason() {
return mFailureReason;
}
@@ -396,7 +394,7 @@
*
* @hide
*/
- public void setFailureReason(CharSequence failureReason) {
+ public void setFailureReason(String failureReason) {
mFailureReason = failureReason;
}
@@ -470,7 +468,7 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mId);
- parcel.writeCharSequence(mLabel);
+ parcel.writeString(mLabel);
parcel.writeParcelable(mPrinterId, flags);
parcel.writeString(mPrinterName);
parcel.writeInt(mState);
@@ -478,12 +476,7 @@
parcel.writeInt(mUserId);
parcel.writeString(mTag);
parcel.writeInt(mCopies);
- if (mFailureReason != null) {
- parcel.writeInt(1);
- parcel.writeCharSequence(mFailureReason);
- } else {
- parcel.writeInt(0);
- }
+ parcel.writeString(mFailureReason);
if (mPageRanges != null) {
parcel.writeInt(1);
parcel.writeParcelableArray(mPageRanges, flags);
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index 636b9d4..531dcb2 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -374,14 +374,14 @@
@Override
public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {
+ if (info == null) {
+ throw new NullPointerException("document info cannot be null");
+ }
final ILayoutResultCallback callback;
synchronized (mLock) {
callback = mCallback;
clearLocked();
}
- if (info == null) {
- throw new IllegalArgumentException("info cannot be null");
- }
if (callback != null) {
try {
callback.onLayoutFinished(info, changed, mSequence);
diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java
index ac782a8..6f567a6 100644
--- a/core/java/android/print/PrinterInfo.java
+++ b/core/java/android/print/PrinterInfo.java
@@ -229,10 +229,11 @@
/**
* Constructor.
*
- * @param prototype Prototype from which to start building.
+ * @param other Other info from which to start building.
*/
- public Builder(PrinterInfo prototype) {
- mPrototype = prototype;
+ public Builder(PrinterInfo other) {
+ mPrototype = new PrinterInfo();
+ mPrototype.copyFrom(other);
}
/**
diff --git a/core/java/android/print/pdf/PdfDocument.java b/core/java/android/print/pdf/PdfDocument.java
index cfeb975..dbd7dd1 100644
--- a/core/java/android/print/pdf/PdfDocument.java
+++ b/core/java/android/print/pdf/PdfDocument.java
@@ -324,7 +324,7 @@
/**
* Creates a new builder with the mandatory page info attributes.
*
- * @param pageSize The page size in pixels.
+ * @param pageSize The page size in points, <strong>not</strong> dips.
* @param pageNumber The page number.
* @param density The page density in DPI.
*/
diff --git a/core/java/android/printservice/IPrintService.aidl b/core/java/android/printservice/IPrintService.aidl
index 16b7a26..2cee1d8 100644
--- a/core/java/android/printservice/IPrintService.aidl
+++ b/core/java/android/printservice/IPrintService.aidl
@@ -16,7 +16,7 @@
package android.printservice;
-import android.print.IPrinterDiscoverySessionObserver;
+import android.print.PrinterId;
import android.print.PrintJobInfo;
import android.printservice.IPrintServiceClient;
@@ -29,5 +29,10 @@
void setClient(IPrintServiceClient client);
void requestCancelPrintJob(in PrintJobInfo printJobInfo);
void onPrintJobQueued(in PrintJobInfo printJobInfo);
- void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer);
+
+ void createPrinterDiscoverySession();
+ void startPrinterDiscovery(in List<PrinterId> priorityList);
+ void stopPrinterDiscovery();
+ void requestPrinterUpdate(in PrinterId printerId);
+ void destroyPrinterDiscoverySession();
}
diff --git a/core/java/android/printservice/IPrintServiceClient.aidl b/core/java/android/printservice/IPrintServiceClient.aidl
index f00b37c4..1e33fc0 100644
--- a/core/java/android/printservice/IPrintServiceClient.aidl
+++ b/core/java/android/printservice/IPrintServiceClient.aidl
@@ -29,7 +29,11 @@
interface IPrintServiceClient {
List<PrintJobInfo> getPrintJobInfos();
PrintJobInfo getPrintJobInfo(int printJobId);
- boolean setPrintJobState(int printJobId, int state, CharSequence error);
+ boolean setPrintJobState(int printJobId, int state, String error);
boolean setPrintJobTag(int printJobId, String tag);
oneway void writePrintJobData(in ParcelFileDescriptor fd, int printJobId);
+
+ void onPrintersAdded(in List<PrinterInfo> printers);
+ void onPrintersRemoved(in List<PrinterId> printerIds);
+ void onPrintersUpdated(in List<PrinterInfo> printers);
}
diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java
index 5f9a730..d2fbef2 100644
--- a/core/java/android/printservice/PrintJob.java
+++ b/core/java/android/printservice/PrintJob.java
@@ -24,6 +24,10 @@
* This class represents a print job from the perspective of a print
* service. It provides APIs for observing the print job state and
* performing operations on the print job.
+ * <p>
+ * <strong>Note: </strong> All methods of this class must be executed on the main
+ * application thread.
+ * </p>
*/
public final class PrintJob {
@@ -48,6 +52,7 @@
* @return The id.
*/
public int getId() {
+ PrintService.throwIfNotCalledOnMainThread();
return mCachedInfo.getId();
}
@@ -62,6 +67,7 @@
* @return The print job info.
*/
public PrintJobInfo getInfo() {
+ PrintService.throwIfNotCalledOnMainThread();
if (isInImmutableState()) {
return mCachedInfo;
}
@@ -83,6 +89,7 @@
* @return The document.
*/
public PrintDocument getDocument() {
+ PrintService.throwIfNotCalledOnMainThread();
return mDocument;
}
@@ -96,6 +103,7 @@
* @see #cancel()
*/
public boolean isQueued() {
+ PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_QUEUED;
}
@@ -110,6 +118,7 @@
* @see #fail(CharSequence)
*/
public boolean isStarted() {
+ PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_STARTED;
}
@@ -122,6 +131,7 @@
* @see #complete()
*/
public boolean isCompleted() {
+ PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_COMPLETED;
}
@@ -134,6 +144,7 @@
* @see #fail(CharSequence)
*/
public boolean isFailed() {
+ PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_FAILED;
}
@@ -146,6 +157,7 @@
* @see #cancel()
*/
public boolean isCancelled() {
+ PrintService.throwIfNotCalledOnMainThread();
return getInfo().getState() == PrintJobInfo.STATE_FAILED;
}
@@ -158,6 +170,7 @@
* @see #isQueued()
*/
public boolean start() {
+ PrintService.throwIfNotCalledOnMainThread();
if (isQueued()) {
return setState(PrintJobInfo.STATE_STARTED, null);
}
@@ -173,6 +186,7 @@
* @see #isStarted()
*/
public boolean complete() {
+ PrintService.throwIfNotCalledOnMainThread();
if (isStarted()) {
return setState(PrintJobInfo.STATE_COMPLETED, null);
}
@@ -191,7 +205,8 @@
* @see #isQueued()
* @see #isStarted()
*/
- public boolean fail(CharSequence error) {
+ public boolean fail(String error) {
+ PrintService.throwIfNotCalledOnMainThread();
if (isQueued() || isStarted()) {
return setState(PrintJobInfo.STATE_FAILED, error);
}
@@ -210,6 +225,7 @@
* @see #isQueued()
*/
public boolean cancel() {
+ PrintService.throwIfNotCalledOnMainThread();
if (isQueued() || isStarted()) {
return setState(PrintJobInfo.STATE_CANCELED, null);
}
@@ -226,6 +242,7 @@
* @return True if the tag was set, false otherwise.
*/
public boolean setTag(String tag) {
+ PrintService.throwIfNotCalledOnMainThread();
if (isInImmutableState()) {
return false;
}
@@ -263,7 +280,7 @@
|| state == PrintJobInfo.STATE_CANCELED;
}
- private boolean setState(int state, CharSequence error) {
+ private boolean setState(int state, String error) {
try {
if (mPrintServiceClient.setPrintJobState(mCachedInfo.getId(), state, error)) {
// Best effort - update the state of the cached info since
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index 92bccd4..f6c0a9a 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -25,7 +25,6 @@
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
-import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrintJobInfo;
import android.print.PrinterId;
import android.util.Log;
@@ -77,8 +76,8 @@
* completion, the print job should be marked as completed by calling {@link
* PrintJob#complete() PrintJob.complete()} after which {@link PrintJob#isCompleted()
* PrintJob.isCompleted()} would return true. In case of a failure, the print job should
- * be marked as failed by calling {@link PrintJob#fail(CharSequence) PrintJob.fail(
- * CharSequence)} after which {@link PrintJob#isFailed() PrintJob.isFailed()} would
+ * be marked as failed by calling {@link PrintJob#fail(String) PrintJob.fail(
+ * String)} after which {@link PrintJob#isFailed() PrintJob.isFailed()} would
* return true.
* </p>
* <p>
@@ -146,6 +145,11 @@
* {@link #SERVICE_META_DATA} and <code><{@link android.R.styleable#PrintService
* print-service}></code>.
* </p>
+ * <p>
+ * <strong>Note: </strong> All callbacks in this class are executed on the main
+ * application thread. You should also invoke any method of this class on the main
+ * application thread.
+ * </p>
*/
public abstract class PrintService extends Service {
@@ -175,14 +179,14 @@
*/
public static final String SERVICE_META_DATA = "android.printservice";
- private final Object mLock = new Object();
-
private Handler mHandler;
private IPrintServiceClient mClient;
private int mLastSessionId = -1;
+ private PrinterDiscoverySession mDiscoverySession;
+
@Override
protected final void attachBaseContext(Context base) {
super.attachBaseContext(base);
@@ -245,21 +249,18 @@
* @see PrintJob#isStarted() PrintJob.isStarted()
*/
public final List<PrintJob> getActivePrintJobs() {
- final IPrintServiceClient client;
- synchronized (mLock) {
- client = mClient;
- }
- if (client == null) {
+ throwIfNotCalledOnMainThread();
+ if (mClient == null) {
return Collections.emptyList();
}
try {
List<PrintJob> printJobs = null;
- List<PrintJobInfo> printJobInfos = client.getPrintJobInfos();
+ List<PrintJobInfo> printJobInfos = mClient.getPrintJobInfos();
if (printJobInfos != null) {
final int printJobInfoCount = printJobInfos.size();
printJobs = new ArrayList<PrintJob>(printJobInfoCount);
for (int i = 0; i < printJobInfoCount; i++) {
- printJobs.add(new PrintJob(printJobInfos.get(i), client));
+ printJobs.add(new PrintJob(printJobInfos.get(i), mClient));
}
}
if (printJobs != null) {
@@ -278,23 +279,50 @@
* @return Global printer id.
*/
public final PrinterId generatePrinterId(String localId) {
+ throwIfNotCalledOnMainThread();
return new PrinterId(new ComponentName(getPackageName(),
getClass().getName()), localId);
}
+ static void throwIfNotCalledOnMainThread() {
+ if (!Looper.getMainLooper().isCurrentThread()) {
+ throw new IllegalAccessError("must be called from the main thread");
+ }
+ }
+
@Override
public final IBinder onBind(Intent intent) {
return new IPrintService.Stub() {
@Override
- public void setClient(IPrintServiceClient client) {
- mHandler.obtainMessage(ServiceHandler.MSG_SET_CLEINT, client)
- .sendToTarget();
+ public void createPrinterDiscoverySession() {
+ mHandler.sendEmptyMessage(ServiceHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION);
}
@Override
- public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
- mHandler.obtainMessage(ServiceHandler.MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION,
- observer).sendToTarget();
+ public void destroyPrinterDiscoverySession() {
+ mHandler.sendEmptyMessage(ServiceHandler.MSG_DESTROY_PRINTER_DISCOVERY_SESSION);
+ }
+
+ public void startPrinterDiscovery(List<PrinterId> priorityList) {
+ mHandler.obtainMessage(ServiceHandler.MSG_START_PRINTER_DISCOVERY,
+ priorityList).sendToTarget();
+ }
+
+ @Override
+ public void stopPrinterDiscovery() {
+ mHandler.sendEmptyMessage(ServiceHandler.MSG_STOP_PRINTER_DISCOVERY);
+ }
+
+ @Override
+ public void requestPrinterUpdate(PrinterId printerId) {
+ mHandler.obtainMessage(ServiceHandler.MSG_REQUEST_PRINTER_UPDATE,
+ printerId).sendToTarget();
+ }
+
+ @Override
+ public void setClient(IPrintServiceClient client) {
+ mHandler.obtainMessage(ServiceHandler.MSG_SET_CLEINT, client)
+ .sendToTarget();
}
@Override
@@ -312,33 +340,62 @@
}
private final class ServiceHandler extends Handler {
- public static final int MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION = 1;
- public static final int MSG_ON_PRINTJOB_QUEUED = 2;
- public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 3;
- public static final int MSG_SET_CLEINT = 4;
+ public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 1;
+ public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2;
+ public static final int MSG_START_PRINTER_DISCOVERY = 3;
+ public static final int MSG_STOP_PRINTER_DISCOVERY = 4;
+ public static final int MSG_REQUEST_PRINTER_UPDATE = 5;
+ public static final int MSG_ON_PRINTJOB_QUEUED = 6;
+ public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 7;
+ public static final int MSG_SET_CLEINT = 8;
public ServiceHandler(Looper looper) {
super(looper, null, true);
}
@Override
+ @SuppressWarnings("unchecked")
public void handleMessage(Message message) {
final int action = message.what;
switch (action) {
- case MSG_ON_CREATE_PRINTER_DISCOVERY_SESSION: {
- IPrinterDiscoverySessionObserver observer =
- (IPrinterDiscoverySessionObserver) message.obj;
+ case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
PrinterDiscoverySession session = onCreatePrinterDiscoverySession();
if (session == null) {
throw new NullPointerException("session cannot be null");
}
- synchronized (mLock) {
- if (session.getId() == mLastSessionId) {
- throw new IllegalStateException("cannot reuse sessions");
- }
- mLastSessionId = session.getId();
+ if (session.getId() == mLastSessionId) {
+ throw new IllegalStateException("cannot reuse session instances");
}
- session.setObserver(observer);
+ mDiscoverySession = session;
+ mLastSessionId = session.getId();
+ session.setObserver(mClient);
+ } break;
+
+ case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: {
+ if (mDiscoverySession != null) {
+ mDiscoverySession.destroy();
+ mDiscoverySession = null;
+ }
+ } break;
+
+ case MSG_START_PRINTER_DISCOVERY: {
+ if (mDiscoverySession != null) {
+ List<PrinterId> priorityList = (ArrayList<PrinterId>) message.obj;
+ mDiscoverySession.startPrinterDiscovery(priorityList);
+ }
+ } break;
+
+ case MSG_STOP_PRINTER_DISCOVERY: {
+ if (mDiscoverySession != null) {
+ mDiscoverySession.stopPrinterDiscovery();
+ }
+ } break;
+
+ case MSG_REQUEST_PRINTER_UPDATE: {
+ if (mDiscoverySession != null) {
+ PrinterId printerId = (PrinterId) message.obj;
+ mDiscoverySession.requestPrinterUpdate(printerId);
+ }
} break;
case MSG_ON_REQUEST_CANCEL_PRINTJOB: {
@@ -352,15 +409,12 @@
} break;
case MSG_SET_CLEINT: {
- IPrintServiceClient client = (IPrintServiceClient) message.obj;
- synchronized (mLock) {
- mClient = client;
- }
- if (client != null) {
+ mClient = (IPrintServiceClient) message.obj;
+ if (mClient != null) {
onConnected();
} else {
onDisconnected();
- }
+ }
} break;
default: {
diff --git a/core/java/android/printservice/PrinterDiscoverySession.java b/core/java/android/printservice/PrinterDiscoverySession.java
index 92dc0dd..8b959a6 100644
--- a/core/java/android/printservice/PrinterDiscoverySession.java
+++ b/core/java/android/printservice/PrinterDiscoverySession.java
@@ -16,18 +16,15 @@
package android.printservice;
-import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
import android.os.RemoteException;
-import android.print.IPrinterDiscoverySessionController;
-import android.print.IPrinterDiscoverySessionObserver;
+import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
+import android.util.ArrayMap;
import android.util.Log;
-import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -36,67 +33,75 @@
* for adding discovered printers, removing already added printers that
* disappeared, and updating already added printers.
* <p>
- * The opening of the session is announced by a call to {@link
- * PrinterDiscoverySession#onOpen(List)} at which point you should start printer
- * discovery. The closing of the session is announced by a call to {@link
- * PrinterDiscoverySession#onClose()} at which point you should stop printer
- * discovery. Discovered printers are added by invoking {@link
- * PrinterDiscoverySession#addPrinters(List)}. Added printers that disappeared
- * are removed by invoking {@link PrinterDiscoverySession#removePrinters(List)}.
- * Added printers whose properties or capabilities changed are updated through
- * a call to {@link PrinterDiscoverySession#updatePrinters(List)}.
+ * During the lifetime of this session you may be asked to start and stop
+ * performing printer discovery multiple times. You will receive a call to {@link
+ * PrinterDiscoverySession#onStartPrinterDiscovery(List)} to start printer
+ * discovery and a call to {@link PrinterDiscoverySession#onStopPrinterDiscovery()}
+ * to stop printer discovery. When the system is no longer interested in printers
+ * discovered by this session you will receive a call to {@link #onDestroy()} at
+ * which point the system will no longer call into the session and all the session
+ * methods will do nothing.
+ * </p>
+ * <p>
+ * Discovered printers are added by invoking {@link
+ * PrinterDiscoverySession#addPrinters(List)}. Added printers that disappeared are
+ * removed by invoking {@link PrinterDiscoverySession#removePrinters(List)}. Added
+ * printers whose properties or capabilities changed are updated through a call to
+ * {@link PrinterDiscoverySession#updatePrinters(List)}. The printers added in this
+ * session can be acquired via {@link #getPrinters()} where the returned printers
+ * will be an up-to-date snapshot of the printers that you reported during the
+ * session. Printers are <strong>not</strong> persisted across sessions.
* </p>
* <p>
* The system will make a call to
* {@link PrinterDiscoverySession#onRequestPrinterUpdate(PrinterId)} if you
* need to update a given printer. It is possible that you add a printer without
- * specifying its capabilities. This enables you to avoid querying all
- * discovered printers for their capabilities, rather querying the capabilities
- * of a printer only if necessary. For example, the system will require that you
- * update a printer if it gets selected by the user. If you did not report the
- * printer capabilities when adding it, you must do so after the system requests
- * a printer update. Otherwise, the printer will be ignored.
+ * specifying its capabilities. This enables you to avoid querying all discovered
+ * printers for their capabilities, rather querying the capabilities of a printer
+ * only if necessary. For example, the system will request that you update a printer
+ * if it gets selected by the user. If you did not report the printer capabilities
+ * when adding it, you must do so after the system requests a printer update.
+ * Otherwise, the printer will be ignored.
* </p>
* <p>
- * During printer discovery all printers that are known to your print service
- * have to be added. The system does not retain any printers from previous
- * sessions.
+ * <strong>Note: </strong> All callbacks in this class are executed on the main
+ * application thread. You also have to invoke any method of this class on the main
+ * application thread.
* </p>
*/
public abstract class PrinterDiscoverySession {
private static final String LOG_TAG = "PrinterDiscoverySession";
+ private static final int MAX_ITEMS_PER_CALLBACK = 100;
+
private static int sIdCounter = 0;
- private final Object mLock = new Object();
-
- private final Handler mHandler;
-
private final int mId;
- private IPrinterDiscoverySessionController mController;
+ private final ArrayMap<PrinterId, PrinterInfo> mPrinters =
+ new ArrayMap<PrinterId, PrinterInfo>();
- private IPrinterDiscoverySessionObserver mObserver;
+ private ArrayMap<PrinterId, PrinterInfo> mLastSentPrinters;
+
+ private IPrintServiceClient mObserver;
+
+ private boolean mIsDestroyed;
+
+ private boolean mIsDiscoveryStarted;
/**
* Constructor.
- *
- * @param context A context instance.
*/
- public PrinterDiscoverySession(Context context) {
+ public PrinterDiscoverySession() {
mId = sIdCounter++;
- mHandler = new SessionHandler(context.getMainLooper());
- mController = new PrinterDiscoverySessionController(this);
}
- void setObserver(IPrinterDiscoverySessionObserver observer) {
- synchronized (mLock) {
- mObserver = observer;
- try {
- mObserver.setController(mController);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error setting session controller", re);
- }
+ void setObserver(IPrintServiceClient observer) {
+ mObserver = observer;
+ // If some printers were added in the method that
+ // created the session, send them over.
+ if (!mPrinters.isEmpty()) {
+ sendAddedPrinters(mObserver, getPrinters());
}
}
@@ -105,131 +110,357 @@
}
/**
+ * Gets the printers reported in this session. For example, if you add two
+ * printers and remove one of them, the returned list will contain only
+ * the printer that was added but not removed.
+ * <p>
+ * <strong>Note: </strong> Calls to this method after the session is
+ * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored.
+ * </p>
+ *
+ * @return The printers.
+ *
+ * @see #addPrinters(List)
+ * @see #removePrinters(List)
+ * @see #updatePrinters(List)
+ * @see #isDestroyed()
+ */
+ public final List<PrinterInfo> getPrinters() {
+ PrintService.throwIfNotCalledOnMainThread();
+ if (mIsDestroyed) {
+ return Collections.emptyList();
+ }
+ return new ArrayList<PrinterInfo>(mPrinters.values());
+ }
+
+ /**
* Adds discovered printers. Adding an already added printer has no effect.
* Removed printers can be added again. You can call this method multiple
- * times during printer discovery.
+ * times during the life of this session. Duplicates will be ignored.
* <p>
- * <strong>Note: </strong> Calls to this method before the session is opened,
- * i.e. before the {@link #onOpen(List)} call, and after the session is closed,
- * i.e. after the call to {@link #onClose()}, will be ignored.
+ * <strong>Note: </strong> Calls to this method after the session is
+ * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored.
* </p>
*
* @param printers The printers to add.
*
* @see #removePrinters(List)
* @see #updatePrinters(List)
+ * @see #getPrinters()
+ * @see #isDestroyed()
*/
public final void addPrinters(List<PrinterInfo> printers) {
- final IPrinterDiscoverySessionObserver observer;
- synchronized (mLock) {
- observer = mObserver;
+ PrintService.throwIfNotCalledOnMainThread();
+
+ // If the session is destroyed - nothing do to.
+ if (mIsDestroyed) {
+ Log.w(LOG_TAG, "Not adding printers - session destroyed.");
+ return;
}
- if (observer != null) {
- try {
- observer.onPrintersAdded(printers);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error adding printers", re);
+
+ if (mIsDiscoveryStarted) {
+ // If during discovery, add the new printers and send them.
+ List<PrinterInfo> addedPrinters = new ArrayList<PrinterInfo>();
+ final int addedPrinterCount = printers.size();
+ for (int i = 0; i < addedPrinterCount; i++) {
+ PrinterInfo addedPrinter = printers.get(i);
+ if (mPrinters.get(addedPrinter.getId()) == null) {
+ mPrinters.put(addedPrinter.getId(), addedPrinter);
+ addedPrinters.add(addedPrinter);
+ }
+ }
+
+ // Send the added printers, if such.
+ if (!addedPrinters.isEmpty()) {
+ sendAddedPrinters(mObserver, addedPrinters);
}
} else {
- Log.w(LOG_TAG, "Printer discovery session not open not adding printers.");
+ // Remember the last sent printers if needed.
+ if (mLastSentPrinters == null) {
+ mLastSentPrinters = new ArrayMap<PrinterId, PrinterInfo>(mPrinters);
+ }
+
+ // Update the printers.
+ final int addedPrinterCount = printers.size();
+ for (int i = 0; i < addedPrinterCount; i++) {
+ PrinterInfo addedPrinter = printers.get(i);
+ if (mPrinters.get(addedPrinter.getId()) == null) {
+ mPrinters.put(addedPrinter.getId(), addedPrinter);
+ }
+ }
+ }
+ }
+
+ private static void sendAddedPrinters(IPrintServiceClient observer,
+ List<PrinterInfo> printers) {
+ try {
+ final int printerCount = printers.size();
+ if (printerCount <= MAX_ITEMS_PER_CALLBACK) {
+ observer.onPrintersAdded(printers);
+ } else {
+ // Send the added printers in chunks avoiding the binder transaction limit.
+ final int transactionCount = (printerCount / MAX_ITEMS_PER_CALLBACK) + 1;
+ for (int i = 0; i < transactionCount; i++) {
+ final int start = i * MAX_ITEMS_PER_CALLBACK;
+ final int end = Math.min(start + MAX_ITEMS_PER_CALLBACK, printerCount);
+ List<PrinterInfo> subPrinters = printers.subList(start, end);
+ observer.onPrintersAdded(subPrinters);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error sending added printers", re);
}
}
/**
* Removes added printers. Removing an already removed or never added
- * printer has no effect. Removed printers can be added again. You
- * can call this method multiple times during printer discovery.
+ * printer has no effect. Removed printers can be added again. You can
+ * call this method multiple times during the lifetime of this session.
* <p>
- * <strong>Note: </strong> Calls to this method before the session is opened,
- * i.e. before the {@link #onOpen(List)} call, and after the session is closed,
- * i.e. after the call to {@link #onClose()}, will be ignored.
+ * <strong>Note: </strong> Calls to this method after the session is
+ * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored.
* </p>
*
* @param printerIds The ids of the removed printers.
*
* @see #addPrinters(List)
* @see #updatePrinters(List)
+ * @see #getPrinters()
+ * @see #isDestroyed()
*/
public final void removePrinters(List<PrinterId> printerIds) {
- final IPrinterDiscoverySessionObserver observer;
- synchronized (mLock) {
- observer = mObserver;
+ PrintService.throwIfNotCalledOnMainThread();
+
+ // If the session is destroyed - nothing do to.
+ if (mIsDestroyed) {
+ Log.w(LOG_TAG, "Not removing printers - session destroyed.");
+ return;
}
- if (observer != null) {
- try {
- observer.onPrintersRemoved(printerIds);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error removing printers", re);
+
+ if (mIsDiscoveryStarted) {
+ // If during discovery, remove existing printers and send them.
+ List<PrinterId> removedPrinterIds = new ArrayList<PrinterId>();
+ final int removedPrinterIdCount = printerIds.size();
+ for (int i = 0; i < removedPrinterIdCount; i++) {
+ PrinterId removedPrinterId = printerIds.get(i);
+ if (mPrinters.remove(removedPrinterId) != null) {
+ removedPrinterIds.add(removedPrinterId);
+ }
+ }
+
+ // Send the removed printers, if such.
+ if (!removedPrinterIds.isEmpty()) {
+ sendRemovedPrinters(mObserver, removedPrinterIds);
}
} else {
- Log.w(LOG_TAG, "Printer discovery session not open not removing printers.");
+ // Remember the last sent printers if needed.
+ if (mLastSentPrinters == null) {
+ mLastSentPrinters = new ArrayMap<PrinterId, PrinterInfo>(mPrinters);
+ }
+
+ // Update the printers.
+ final int removedPrinterIdCount = printerIds.size();
+ for (int i = 0; i < removedPrinterIdCount; i++) {
+ PrinterId removedPrinterId = printerIds.get(i);
+ mPrinters.remove(removedPrinterId);
+ }
+ }
+ }
+
+ private static void sendRemovedPrinters(IPrintServiceClient observer,
+ List<PrinterId> printerIds) {
+ try {
+ final int printerIdCount = printerIds.size();
+ if (printerIdCount <= MAX_ITEMS_PER_CALLBACK) {
+ observer.onPrintersRemoved(printerIds);
+ } else {
+ final int transactionCount = (printerIdCount / MAX_ITEMS_PER_CALLBACK) + 1;
+ for (int i = 0; i < transactionCount; i++) {
+ final int start = i * MAX_ITEMS_PER_CALLBACK;
+ final int end = Math.min(start + MAX_ITEMS_PER_CALLBACK, printerIdCount);
+ List<PrinterId> subPrinterIds = printerIds.subList(start, end);
+ observer.onPrintersRemoved(subPrinterIds);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error sending removed printers", re);
}
}
/**
* Updates added printers. Updating a printer that was not added or that
* was removed has no effect. You can call this method multiple times
- * during printer discovery.
+ * during the lifetime of this session.
* <p>
- * <strong>Note: </strong> Calls to this method before the session is opened,
- * i.e. before the {@link #onOpen(List)} call, and after the session is closed,
- * i.e. after the call to {@link #onClose()}, will be ignored.
+ * <strong>Note: </strong> Calls to this method after the session is
+ * destroyed, i.e. after the {@link #onDestroy()} callback, will be ignored.
* </p>
*
* @param printers The printers to update.
*
* @see #addPrinters(List)
* @see #removePrinters(List)
+ * @see #getPrinters()
+ * @see #isDestroyed()
*/
public final void updatePrinters(List<PrinterInfo> printers) {
- final IPrinterDiscoverySessionObserver observer;
- synchronized (mLock) {
- observer = mObserver;
+ PrintService.throwIfNotCalledOnMainThread();
+
+ // If the session is destroyed - nothing do to.
+ if (mIsDestroyed) {
+ Log.w(LOG_TAG, "Not updating printers - session destroyed.");
+ return;
}
- if (observer != null) {
- try {
- observer.onPrintersUpdated(printers);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error updating printers", re);
+
+ if (mIsDiscoveryStarted) {
+ // If during discovery, update existing printers and send them.
+ List<PrinterInfo> updatedPrinters = new ArrayList<PrinterInfo>();
+ final int updatedPrinterCount = printers.size();
+ for (int i = 0; i < updatedPrinterCount; i++) {
+ PrinterInfo updatedPrinter = printers.get(i);
+ PrinterInfo oldPrinter = mPrinters.get(updatedPrinter.getId());
+ if (oldPrinter != null && !oldPrinter.equals(updatedPrinter)) {
+ mPrinters.put(updatedPrinter.getId(), updatedPrinter);
+ updatedPrinters.add(updatedPrinter);
+ }
+ }
+
+ // Send the updated printers, if such.
+ if (!updatedPrinters.isEmpty()) {
+ sendUpdatedPrinters(mObserver, updatedPrinters);
}
} else {
- Log.w(LOG_TAG, "Printer discovery session not open not updating printers.");
+ // Remember the last sent printers if needed.
+ if (mLastSentPrinters == null) {
+ mLastSentPrinters = new ArrayMap<PrinterId, PrinterInfo>(mPrinters);
+ }
+
+ // Update the printers.
+ final int updatedPrinterCount = printers.size();
+ for (int i = 0; i < updatedPrinterCount; i++) {
+ PrinterInfo updatedPrinter = printers.get(i);
+ PrinterInfo oldPrinter = mPrinters.get(updatedPrinter.getId());
+ if (oldPrinter != null && !oldPrinter.equals(updatedPrinter)) {
+ mPrinters.put(updatedPrinter.getId(), updatedPrinter);
+ }
+ }
}
}
+ private static void sendUpdatedPrinters(IPrintServiceClient observer,
+ List<PrinterInfo> printers) {
+ try {
+ final int printerCount = printers.size();
+ if (printerCount <= MAX_ITEMS_PER_CALLBACK) {
+ observer.onPrintersUpdated(printers);
+ } else {
+ final int transactionCount = (printerCount / MAX_ITEMS_PER_CALLBACK) + 1;
+ for (int i = 0; i < transactionCount; i++) {
+ final int start = i * MAX_ITEMS_PER_CALLBACK;
+ final int end = Math.min(start + MAX_ITEMS_PER_CALLBACK, printerCount);
+ List<PrinterInfo> subPrinters = printers.subList(start, end);
+ observer.onPrintersUpdated(subPrinters);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error sending updated printers", re);
+ }
+ }
+
+ private void sendOutOfDiscoveryPeriodPrinterChanges() {
+ // Noting changed since the last discovery period - nothing to do.
+ if (mLastSentPrinters == null || mLastSentPrinters.isEmpty()) {
+ mLastSentPrinters = null;
+ return;
+ }
+
+ List<PrinterInfo> addedPrinters = null;
+ List<PrinterInfo> updatedPrinters = null;
+ List<PrinterId> removedPrinterIds = null;
+
+ // Determine the added and updated printers.
+ for (PrinterInfo printer : mPrinters.values()) {
+ PrinterInfo sentPrinter = mLastSentPrinters.get(printer.getId());
+ if (sentPrinter != null) {
+ if (!sentPrinter.equals(printer)) {
+ if (updatedPrinters == null) {
+ updatedPrinters = new ArrayList<PrinterInfo>();
+ }
+ updatedPrinters.add(printer);
+ }
+ } else {
+ if (addedPrinters == null) {
+ addedPrinters = new ArrayList<PrinterInfo>();
+ }
+ addedPrinters.add(printer);
+ }
+ }
+
+ // Send the added printers, if such.
+ if (addedPrinters != null) {
+ sendAddedPrinters(mObserver, addedPrinters);
+ }
+
+ // Send the updated printers, if such.
+ if (updatedPrinters != null) {
+ sendUpdatedPrinters(mObserver, updatedPrinters);
+ }
+
+ // Determine the removed printers.
+ for (PrinterInfo sentPrinter : mLastSentPrinters.values()) {
+ if (!mPrinters.containsKey(sentPrinter.getId())) {
+ if (removedPrinterIds == null) {
+ removedPrinterIds = new ArrayList<PrinterId>();
+ }
+ removedPrinterIds.add(sentPrinter.getId());
+ }
+ }
+
+ // Send the removed printers, if such.
+ if (removedPrinterIds != null) {
+ sendRemovedPrinters(mObserver, removedPrinterIds);
+ }
+
+ mLastSentPrinters = null;
+ }
+
/**
- * Callback notifying you that the session is open and you should start
- * printer discovery. Discovered printers should be added via calling
- * {@link #addPrinters(List)}. Added printers that disappeared should be
- * removed via calling {@link #removePrinters(List)}. Added printers whose
- * properties or capabilities changes should be updated via calling {@link
- * #updatePrinters(List)}. When the session is closed you will receive a
- * call to {@link #onClose()}.
+ * Callback asking you to start printer discovery. Discovered printers should be
+ * added via calling {@link #addPrinters(List)}. Added printers that disappeared
+ * should be removed via calling {@link #removePrinters(List)}. Added printers
+ * whose properties or capabilities changed should be updated via calling {@link
+ * #updatePrinters(List)}. You will receive a call to call to {@link
+ * #onStopPrinterDiscovery()} when you should stop printer discovery.
* <p>
- * During printer discovery all printers that are known to your print
- * service have to be added. The system does not retain any printers from
- * previous sessions.
+ * During the lifetime of this session all printers that are known to your print
+ * service have to be added. The system does not retain any printers across sessions.
+ * However, if you were asked to start and then stop performing printer discovery
+ * in this session, then a subsequent discovering should not re-discover already
+ * discovered printers.
* </p>
* <p>
- * <strong>Note: </strong>You are also given a list of printers whose
- * availability has to be checked first. For example, these printers could
- * be the user's favorite ones, therefore they have to be verified first.
+ * <strong>Note: </strong>You are also given a list of printers whose availability
+ * has to be checked first. For example, these printers could be the user's favorite
+ * ones, therefore they have to be verified first.
* </p>
*
- * @see #onClose()
+ * @param priorityList The list of printers to validate first. Never null.
+ *
+ * @see #onStopPrinterDiscovery()
* @see #addPrinters(List)
* @see #removePrinters(List)
* @see #updatePrinters(List)
+ * @see #isPrinterDiscoveryStarted()
*/
- public abstract void onOpen(List<PrinterId> priorityList);
+ public abstract void onStartPrinterDiscovery(List<PrinterId> priorityList);
/**
- * Callback notifying you that the session is closed and you should stop
- * printer discovery. After the session is closed any call to the methods
- * of this instance will be ignored. Once the session is closed
- * it will never be opened again.
+ * Callback notifying you that you should stop printer discovery.
+ *
+ * @see #onStartPrinterDiscovery(List)
+ * @see #isPrinterDiscoveryStarted()
*/
- public abstract void onClose();
+ public abstract void onStopPrinterDiscovery();
/**
* Requests that you update a printer. You are responsible for updating
@@ -255,77 +486,72 @@
*/
public abstract void onRequestPrinterUpdate(PrinterId printerId);
- void close() {
- synchronized (mLock) {
- mController = null;
+ /**
+ * Notifies you that the session is destroyed. After this callback is invoked
+ * any calls to the methods of this class will be ignored, {@link #isDestroyed()}
+ * will return true and you will also no longer receive callbacks.
+ *
+ * @see #isDestroyed()
+ */
+ public abstract void onDestroy();
+
+ /**
+ * Gets whether the session is destroyed.
+ *
+ * @return Whether the session is destroyed.
+ *
+ * @see #onDestroy()
+ */
+ public final boolean isDestroyed() {
+ PrintService.throwIfNotCalledOnMainThread();
+ return mIsDestroyed;
+ }
+
+ /**
+ * Gets whether printer discovery is started.
+ *
+ * @return Whether printer discovery is destroyed.
+ *
+ * @see #onStartPrinterDiscovery(List)
+ * @see #onStopPrinterDiscovery()
+ */
+ public final boolean isPrinterDiscoveryStarted() {
+ PrintService.throwIfNotCalledOnMainThread();
+ return mIsDiscoveryStarted;
+ }
+
+ void startPrinterDiscovery(List<PrinterId> priorityList) {
+ if (!mIsDestroyed) {
+ mIsDiscoveryStarted = true;
+ sendOutOfDiscoveryPeriodPrinterChanges();
+ if (priorityList == null) {
+ priorityList = Collections.emptyList();
+ }
+ onStartPrinterDiscovery(priorityList);
+ }
+ }
+
+ void stopPrinterDiscovery() {
+ if (!mIsDestroyed) {
+ mIsDiscoveryStarted = false;
+ onStopPrinterDiscovery();
+ }
+ }
+
+ void requestPrinterUpdate(PrinterId printerId) {
+ if (!mIsDestroyed) {
+ onRequestPrinterUpdate(printerId);
+ }
+ }
+
+ void destroy() {
+ if (!mIsDestroyed) {
+ mIsDestroyed = true;
+ mIsDiscoveryStarted = false;
+ mPrinters.clear();
+ mLastSentPrinters = null;
mObserver = null;
+ onDestroy();
}
}
-
- private final class SessionHandler extends Handler {
- public static final int MSG_OPEN = 1;
- public static final int MSG_CLOSE = 2;
- public static final int MSG_REQUEST_PRINTER_UPDATE = 3;
-
- public SessionHandler(Looper looper) {
- super(looper, null, true);
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public void handleMessage(Message message) {
- switch (message.what) {
- case MSG_OPEN: {
- List<PrinterId> priorityList = (List<PrinterId>) message.obj;
- onOpen(priorityList);
- } break;
-
- case MSG_CLOSE: {
- onClose();
- close();
- } break;
-
- case MSG_REQUEST_PRINTER_UPDATE: {
- PrinterId printerId = (PrinterId) message.obj;
- onRequestPrinterUpdate(printerId);
- } break;
- }
- }
- }
-
- private static final class PrinterDiscoverySessionController extends
- IPrinterDiscoverySessionController.Stub {
- private final WeakReference<PrinterDiscoverySession> mWeakSession;
-
- public PrinterDiscoverySessionController(PrinterDiscoverySession session) {
- mWeakSession = new WeakReference<PrinterDiscoverySession>(session);
- }
-
- @Override
- public void open(List<PrinterId> priorityList) {
- PrinterDiscoverySession session = mWeakSession.get();
- if (session != null) {
- session.mHandler.obtainMessage(SessionHandler.MSG_OPEN,
- priorityList).sendToTarget();
- }
- }
-
- @Override
- public void close() {
- PrinterDiscoverySession session = mWeakSession.get();
- if (session != null) {
- session.mHandler.sendEmptyMessage(SessionHandler.MSG_CLOSE);
- }
- }
-
- @Override
- public void requestPrinterUpdate(PrinterId printerId) {
- PrinterDiscoverySession session = mWeakSession.get();
- if (session != null) {
- session.mHandler.obtainMessage(
- SessionHandler.MSG_REQUEST_PRINTER_UPDATE,
- printerId).sendToTarget();
- }
- }
- };
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 585115a..fad6c73 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3269,6 +3269,23 @@
public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed";
/**
+ * Location access disabled
+ */
+ public static final int LOCATION_MODE_OFF = 0;
+ /**
+ * Network Location Provider disabled, but GPS and other sensors enabled.
+ */
+ public static final int LOCATION_MODE_SENSORS_ONLY = 1;
+ /**
+ * Reduced power usage, such as limiting the number of GPS updates per hour.
+ */
+ public static final int LOCATION_MODE_BATTERY_SAVING = 2;
+ /**
+ * Best-effort location computation allowed.
+ */
+ public static final int LOCATION_MODE_HIGH_ACCURACY = 3;
+
+ /**
* A flag containing settings used for biometric weak
* @hide
*/
@@ -4319,23 +4336,27 @@
* @param cr the content resolver to use
* @param provider the location provider to query
* @return true if the provider is enabled
+ * @deprecated use {@link #getLocationMode(ContentResolver)}
*/
+ @Deprecated
public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) {
return isLocationProviderEnabledForUser(cr, provider, UserHandle.myUserId());
}
/**
* Helper method for determining if the location master switch is enabled.
+ *
+ * TODO: worth retaining this method?
+ *
* @param cr the content resolver to use
* @return true if the master switch is enabled
+ * @deprecated use {@link #getLocationMode(ContentResolver)} != {@link #LOCATION_MODE_OFF}
* @hide
*/
+ @Deprecated
public static final boolean isLocationMasterSwitchEnabled(ContentResolver cr) {
- int uid = UserHandle.myUserId();
- synchronized (mLocationSettingsLock) {
- return isLocationProviderEnabledForUser(cr, LocationManager.NETWORK_PROVIDER, uid)
- || isLocationProviderEnabledForUser(cr, LocationManager.GPS_PROVIDER, uid);
- }
+ int mode = getLocationMode(cr);
+ return mode != LOCATION_MODE_OFF;
}
/**
@@ -4344,8 +4365,10 @@
* @param provider the location provider to query
* @param userId the userId to query
* @return true if the provider is enabled
+ * @deprecated use {@link #getLocationModeForUser(ContentResolver, int)}
* @hide
*/
+ @Deprecated
public static final boolean isLocationProviderEnabledForUser(ContentResolver cr, String provider, int userId) {
String allowedProviders = Settings.Secure.getStringForUser(cr,
LOCATION_PROVIDERS_ALLOWED, userId);
@@ -4357,7 +4380,9 @@
* @param cr the content resolver to use
* @param provider the location provider to enable or disable
* @param enabled true if the provider should be enabled
+ * @deprecated use {@link #setLocationMode(ContentResolver, int)}
*/
+ @Deprecated
public static final void setLocationProviderEnabled(ContentResolver cr,
String provider, boolean enabled) {
setLocationProviderEnabledForUser(cr, provider, enabled, UserHandle.myUserId());
@@ -4368,8 +4393,11 @@
*
* @param cr the content resolver to use
* @param enabled true if master switch should be enabled
+ * @deprecated use {@link #setLocationMode(ContentResolver, int)} with
+ * {@link #LOCATION_MODE_HIGH_ACCURACY}
* @hide
*/
+ @Deprecated
public static final void setLocationMasterSwitchEnabled(ContentResolver cr,
boolean enabled) {
int uid = UserHandle.myUserId();
@@ -4386,8 +4414,10 @@
* @param provider the location provider to enable or disable
* @param enabled true if the provider should be enabled
* @param userId the userId for which to enable/disable providers
+ * @deprecated use {@link #setLocationModeForUser(ContentResolver, int, int)}
* @hide
*/
+ @Deprecated
public static final void setLocationProviderEnabledForUser(ContentResolver cr,
String provider, boolean enabled, int userId) {
synchronized (mLocationSettingsLock) {
@@ -4403,6 +4433,97 @@
userId);
}
}
+
+ /**
+ * Thread-safe method for setting the location mode to one of
+ * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY},
+ * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}.
+ *
+ * @param cr the content resolver to use
+ * @param mode such as {@link #LOCATION_MODE_HIGH_ACCURACY}
+ * @param userId the userId for which to change mode
+ *
+ * @throws IllegalArgumentException if mode is not one of the supported values
+ */
+ public static final void setLocationModeForUser(ContentResolver cr, int mode, int userId) {
+ synchronized (mLocationSettingsLock) {
+ boolean gps = false;
+ boolean network = false;
+ switch (mode) {
+ case LOCATION_MODE_OFF:
+ break;
+ case LOCATION_MODE_SENSORS_ONLY:
+ gps = true;
+ break;
+ case LOCATION_MODE_BATTERY_SAVING:
+ network = true;
+ break;
+ case LOCATION_MODE_HIGH_ACCURACY:
+ gps = true;
+ network = true;
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid location mode: " + mode);
+ }
+ Settings.Secure.setLocationProviderEnabledForUser(
+ cr, LocationManager.GPS_PROVIDER, gps, userId);
+ Settings.Secure.setLocationProviderEnabledForUser(
+ cr, LocationManager.NETWORK_PROVIDER, network, userId);
+ }
+ }
+
+ /**
+ * Thread-safe method for setting the location mode to one of
+ * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY},
+ * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}.
+ *
+ * @param cr the content resolver to use
+ * @param mode such as {@link #LOCATION_MODE_HIGH_ACCURACY}
+ *
+ * @throws IllegalArgumentException if mode is not one of the supported values
+ */
+ public static final void setLocationMode(ContentResolver cr, int mode) {
+ setLocationModeForUser(cr, mode, UserHandle.myUserId());
+ }
+
+ /**
+ * Thread-safe method for reading the location mode, returns one of
+ * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY},
+ * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}.
+ *
+ * @param cr the content resolver to use
+ * @param userId the userId for which to read the mode
+ * @return the location mode
+ */
+ public static final int getLocationModeForUser(ContentResolver cr, int userId) {
+ synchronized (mLocationSettingsLock) {
+ boolean gpsEnabled = Settings.Secure.isLocationProviderEnabledForUser(
+ cr, LocationManager.GPS_PROVIDER, userId);
+ boolean networkEnabled = Settings.Secure.isLocationProviderEnabledForUser(
+ cr, LocationManager.NETWORK_PROVIDER, userId);
+ if (gpsEnabled && networkEnabled) {
+ return LOCATION_MODE_HIGH_ACCURACY;
+ } else if (gpsEnabled) {
+ return LOCATION_MODE_SENSORS_ONLY;
+ } else if (networkEnabled) {
+ return LOCATION_MODE_BATTERY_SAVING;
+ } else {
+ return LOCATION_MODE_OFF;
+ }
+ }
+ }
+
+ /**
+ * Thread-safe method for reading the location mode, returns one of
+ * {@link #LOCATION_MODE_HIGH_ACCURACY}, {@link #LOCATION_MODE_SENSORS_ONLY},
+ * {@link #LOCATION_MODE_BATTERY_SAVING}, or {@link #LOCATION_MODE_OFF}.
+ *
+ * @param cr the content resolver to use
+ * @return the location mode
+ */
+ public static final int getLocationMode(ContentResolver cr) {
+ return getLocationModeForUser(cr, UserHandle.myUserId());
+ }
}
/**
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index e0786f7..409db84 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -36,7 +36,7 @@
throws OutOfResourcesException;
private static native int nativeCreateFromSurfaceControl(int surfaceControlNativeObject);
- private static native void nativeLockCanvas(int nativeObject, Canvas canvas, Rect dirty)
+ private static native int nativeLockCanvas(int nativeObject, Canvas canvas, Rect dirty)
throws OutOfResourcesException;
private static native void nativeUnlockCanvasAndPost(int nativeObject, Canvas canvas);
@@ -72,6 +72,7 @@
final Object mLock = new Object(); // protects the native state
private String mName;
int mNativeObject; // package scope only for SurfaceControl access
+ private int mLockedObject;
private int mGenerationId; // incremented each time mNativeObject changes
private final Canvas mCanvas = new CompatibleCanvas();
@@ -233,7 +234,14 @@
throws OutOfResourcesException, IllegalArgumentException {
synchronized (mLock) {
checkNotReleasedLocked();
- nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
+ if (mLockedObject != 0) {
+ // Ideally, nativeLockCanvas() would throw in this situation and prevent the
+ // double-lock, but that won't happen if mNativeObject was updated. We can't
+ // abandon the old mLockedObject because it might still be in use, so instead
+ // we just refuse to re-lock the Surface.
+ throw new RuntimeException("Surface was already locked");
+ }
+ mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
return mCanvas;
}
}
@@ -252,11 +260,21 @@
synchronized (mLock) {
checkNotReleasedLocked();
- nativeUnlockCanvasAndPost(mNativeObject, canvas);
+ if (mNativeObject != mLockedObject) {
+ Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
+ Integer.toHexString(mNativeObject) + ") != mLockedObject (0x" +
+ Integer.toHexString(mLockedObject) +")");
+ }
+ if (mLockedObject == 0) {
+ throw new RuntimeException("Surface was not locked");
+ }
+ nativeUnlockCanvasAndPost(mLockedObject, canvas);
+ nativeRelease(mLockedObject);
+ mLockedObject = 0;
}
}
- /**
+ /**
* @deprecated This API has been removed and is not supported. Do not use.
*/
@Deprecated
@@ -343,6 +361,10 @@
}
synchronized (mLock) {
+ // nativeReadFromParcel() will either return mNativeObject, or
+ // create a new native Surface and return it after reducing
+ // the reference count on mNativeObject. Either way, it is
+ // not necessary to call nativeRelease() here.
mName = source.readString();
setNativeObjectLocked(nativeReadFromParcel(mNativeObject, source));
}
@@ -365,7 +387,8 @@
@Override
public String toString() {
synchronized (mLock) {
- return "Surface(name=" + mName + ")";
+ return "Surface(name=" + mName + ")/@0x" +
+ Integer.toHexString(System.identityHashCode(this));
}
}
@@ -463,7 +486,7 @@
public void getMatrix(Matrix m) {
super.getMatrix(m);
if (mOrigMatrix == null) {
- mOrigMatrix = new Matrix();
+ mOrigMatrix = new Matrix();
}
mOrigMatrix.set(m);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index f711e7a..354e815 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5960,6 +5960,11 @@
// Do nothing.
}
+ void changeCanvasOpacity(boolean opaque) {
+ // TODO(romainguy): recreate Canvas (software or hardware) to reflect the opacity change.
+ Log.d(TAG, "changeCanvasOpacity: opaque=" + opaque);
+ }
+
class TakenSurfaceHolder extends BaseSurfaceHolder {
@Override
public boolean onAllowLockCanvas() {
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 3dd96f5..96c0ed2 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -494,6 +494,21 @@
}
}
}
+
+ /** @hide */
+ public void changeCanvasOpacity(IBinder token, boolean opaque) {
+ if (token == null) {
+ return;
+ }
+ synchronized (mLock) {
+ for (int i = mParams.size() - 1; i >= 0; --i) {
+ if (mParams.get(i).token == token) {
+ mRoots.get(i).changeCanvasOpacity(opaque);
+ return;
+ }
+ }
+ }
+ }
}
final class WindowLeaked extends AndroidRuntimeException {
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 03bde70..1b57d50 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -28,6 +28,7 @@
import android.net.http.SslCertificate;
import android.os.Build;
import android.os.Bundle;
+import android.os.CancellationSignal;
import android.os.Looper;
import android.os.Message;
import android.os.StrictMode;
@@ -1043,24 +1044,26 @@
* @param width The page width. Should be larger than 0.
* @param height The page height. Should be larger than 0.
* @param resultCallback A callback to be invoked when the PDF content is exported.
- * A true indicates success, and a false failure.
+ * A true indicates success, and a false failure. Cannot be null.
+ * @param cancellationSignal Signal for cancelling the PDF conversion request. Must not
+ * be null.
*
- * TODO: explain method parameters, margins, consider making the callback
- * return more meaningful information, explain any threading concerns, HW
- * draw limitations, and make it public.
- * TODO: at the moment we are asking app to provide paper size information (width
- * and height). This is likely not ideal (I think need margin info too).
- * Another approach would be using PrintAttributes. This is to be clarified later.
+ * The PDF conversion is done asynchronously and the PDF output is written to the provided
+ * outputstream. The caller should not close the outputstream until the resultCallback is
+ * called, indicating PDF conversion is complete. Webview cannot be drawn during the pdf
+ * export so the application is recommended to take it offscreen, or putting in a layer
+ * with an overlaid progress UI / spinner.
*
- * TODO: explain this webview will not draw during export (onDraw will clear to
- * background color) so recommend taking it offscreen, or putting in a layer with an
- * overlaid progress UI / spinner.
+ * If the caller cancels the task using the cancellationSignal, the cancellation will be
+ * acked using the resultCallback signal.
+ *
+ * TODO(sgurun) margins, explain the units, make it public.
* @hide
*/
public void exportToPdf(OutputStream out, int width, int height,
- ValueCallback<Boolean> resultCallback) {
+ ValueCallback<Boolean> resultCallback, CancellationSignal cancellationSignal) {
checkThread();
- mProvider.exportToPdf(out, width, height, resultCallback);
+ mProvider.exportToPdf(out, width, height, resultCallback, cancellationSignal);
}
/**
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index 228de5b..db98d30 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -57,6 +57,7 @@
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -2896,7 +2897,7 @@
*/
@Override
public void exportToPdf(java.io.OutputStream out, int width, int height,
- ValueCallback<Boolean> resultCallback) {
+ ValueCallback<Boolean> resultCallback, CancellationSignal cancellationSignal) {
// K-only API not implemented in WebViewClassic.
throw new IllegalStateException("This API not supported on Android 4.3 and earlier");
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 17b4061..8fe6edf 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -25,6 +25,7 @@
import android.graphics.drawable.Drawable;
import android.net.http.SslCertificate;
import android.os.Bundle;
+import android.os.CancellationSignal;
import android.os.Message;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -148,7 +149,7 @@
public Picture capturePicture();
public void exportToPdf(OutputStream out, int width, int height,
- ValueCallback<Boolean> resultCallback);
+ ValueCallback<Boolean> resultCallback, CancellationSignal cancellationSignal);
public float getScale();
diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java
index b442ff5..d9e3ef6 100644
--- a/core/java/com/android/internal/os/HandlerCaller.java
+++ b/core/java/com/android/internal/os/HandlerCaller.java
@@ -65,7 +65,11 @@
mH.sendMessage(msg);
}
-
+
+ public void sendMessageDelayed(Message msg, long delayMillis) {
+ mH.sendMessageDelayed(msg, delayMillis);
+ }
+
public boolean hasMessages(int what) {
return mH.hasMessages(what);
}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index d6c5e4f..7d99464 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -41,11 +41,15 @@
static int check_AudioSystem_Command(status_t status)
{
- if (status == NO_ERROR) {
+ switch (status) {
+ case DEAD_OBJECT:
+ return kAudioStatusMediaServerDied;
+ case NO_ERROR:
return kAudioStatusOk;
- } else {
- return kAudioStatusError;
+ default:
+ break;
}
+ return kAudioStatusError;
}
static int
@@ -112,61 +116,19 @@
return env->NewStringUTF(AudioSystem::getParameters(0, c_keys8).string());
}
-static JNIEnv* AudioSystem_getJNIEnv(bool* needsDetach) {
- *needsDetach = false;
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- if (env == NULL) {
- JavaVMAttachArgs args = {JNI_VERSION_1_4, NULL, NULL};
- JavaVM* vm = AndroidRuntime::getJavaVM();
- int result = vm->AttachCurrentThread(&env, (void*) &args);
- if (result != JNI_OK) {
- ALOGE("thread attach failed: %#x", result);
- return NULL;
- }
- *needsDetach = true;
- }
- return env;
-}
-
-static void AudioSystem_detachJNI() {
- JavaVM* vm = AndroidRuntime::getJavaVM();
- int result = vm->DetachCurrentThread();
- if (result != JNI_OK) {
- ALOGE("thread detach failed: %#x", result);
- }
-}
-
static void
android_media_AudioSystem_error_callback(status_t err)
{
- bool needsDetach = false;
- JNIEnv *env = AudioSystem_getJNIEnv(&needsDetach);
-
+ JNIEnv *env = AndroidRuntime::getJNIEnv();
if (env == NULL) {
return;
}
jclass clazz = env->FindClass(kClassPathName);
- int error;
-
- switch (err) {
- case DEAD_OBJECT:
- error = kAudioStatusMediaServerDied;
- break;
- case NO_ERROR:
- error = kAudioStatusOk;
- break;
- default:
- error = kAudioStatusError;
- break;
- }
-
- env->CallStaticVoidMethod(clazz, env->GetStaticMethodID(clazz, "errorCallbackFromNative","(I)V"), error);
-
- if (needsDetach) {
- AudioSystem_detachJNI();
- }
+ env->CallStaticVoidMethod(clazz, env->GetStaticMethodID(clazz,
+ "errorCallbackFromNative","(I)V"),
+ check_AudioSystem_Command(err));
}
static int
@@ -313,6 +275,12 @@
return (jint) AudioSystem::setLowRamDevice((bool) isLowRamDevice);
}
+static int
+android_media_AudioSystem_checkAudioFlinger(JNIEnv *env, jobject clazz)
+{
+ return check_AudioSystem_Command(AudioSystem::checkAudioFlinger());
+}
+
// ----------------------------------------------------------------------------
static JNINativeMethod gMethods[] = {
@@ -340,6 +308,7 @@
{"getPrimaryOutputFrameCount", "()I", (void *)android_media_AudioSystem_getPrimaryOutputFrameCount},
{"getOutputLatency", "(I)I", (void *)android_media_AudioSystem_getOutputLatency},
{"setLowRamDevice", "(Z)I", (void *)android_media_AudioSystem_setLowRamDevice},
+ {"checkAudioFlinger", "()I", (void *)android_media_AudioSystem_checkAudioFlinger},
};
int register_android_media_AudioSystem(JNIEnv *env)
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 304514b..3f54fd7 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -196,13 +196,13 @@
SkSafeUnref(previousCanvas);
}
-static void nativeLockCanvas(JNIEnv* env, jclass clazz,
+static jint nativeLockCanvas(JNIEnv* env, jclass clazz,
jint nativeObject, jobject canvasObj, jobject dirtyRectObj) {
sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
if (!isSurfaceValid(surface)) {
doThrowIAE(env);
- return;
+ return 0;
}
Rect dirtyRect;
@@ -223,7 +223,7 @@
OutOfResourcesException :
"java/lang/IllegalArgumentException";
jniThrowException(env, exception, NULL);
- return;
+ return 0;
}
// Associate a SkCanvas object to this surface
@@ -255,6 +255,13 @@
env->SetIntField(dirtyRectObj, gRectClassInfo.right, dirtyRect.right);
env->SetIntField(dirtyRectObj, gRectClassInfo.bottom, dirtyRect.bottom);
}
+
+ // Create another reference to the surface and return it. This reference
+ // should be passed to nativeUnlockCanvasAndPost in place of mNativeObject,
+ // because the latter could be replaced while the surface is locked.
+ sp<Surface> lockedSurface(surface);
+ lockedSurface->incStrong(&sRefBaseOwner);
+ return (int) lockedSurface.get();
}
static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz,
@@ -351,7 +358,7 @@
(void*)nativeIsValid },
{"nativeIsConsumerRunningBehind", "(I)Z",
(void*)nativeIsConsumerRunningBehind },
- {"nativeLockCanvas", "(ILandroid/graphics/Canvas;Landroid/graphics/Rect;)V",
+ {"nativeLockCanvas", "(ILandroid/graphics/Canvas;Landroid/graphics/Rect;)I",
(void*)nativeLockCanvas },
{"nativeUnlockCanvasAndPost", "(ILandroid/graphics/Canvas;)V",
(void*)nativeUnlockCanvasAndPost },
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1363e3c..33b77cc 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -218,6 +218,7 @@
<protected-broadcast android:name="android.location.GPS_ENABLED_CHANGE" />
<protected-broadcast android:name="android.location.PROVIDERS_CHANGED" />
<protected-broadcast android:name="android.location.GPS_FIX_CHANGE" />
+ <protected-broadcast android:name="android.net.proxy.PAC_REFRESH" />
<!-- ====================================== -->
<!-- Permissions for things that cost money -->
@@ -1617,6 +1618,14 @@
android:label="@string/permlab_anyCodecForPlayback"
android:description="@string/permdesc_anyCodecForPlayback" />
+ <!-- Allows an application to install and/or uninstall CA certificates on
+ behalf of the user.
+ @hide -->
+ <permission android:name="android.permission.MANAGE_CA_CERTIFICATES"
+ android:protectionLevel="signature|system"
+ android:label="@string/permlab_manageCaCertificates"
+ android:description="@string/permdesc_manageCaCertificates" />
+
<!-- ========================================= -->
<!-- Permissions for special development tools -->
<!-- ========================================= -->
diff --git a/core/res/res/drawable/spinner_ab_holo_dark.xml b/core/res/res/drawable/spinner_ab_holo_dark.xml
index 0932eff..9b8967c 100644
--- a/core/res/res/drawable/spinner_ab_holo_dark.xml
+++ b/core/res/res/drawable/spinner_ab_holo_dark.xml
@@ -14,7 +14,8 @@
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="true">
<item android:state_enabled="false"
android:drawable="@drawable/spinner_ab_disabled_holo_dark" />
<item android:state_pressed="true"
diff --git a/core/res/res/drawable/spinner_ab_holo_light.xml b/core/res/res/drawable/spinner_ab_holo_light.xml
index e785cf4..a324c08 100644
--- a/core/res/res/drawable/spinner_ab_holo_light.xml
+++ b/core/res/res/drawable/spinner_ab_holo_light.xml
@@ -14,7 +14,8 @@
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="true">
<item android:state_enabled="false"
android:drawable="@drawable/spinner_ab_disabled_holo_light" />
<item android:state_pressed="true"
diff --git a/core/res/res/drawable/spinner_background_holo_dark.xml b/core/res/res/drawable/spinner_background_holo_dark.xml
index eb6b18b..a57f720 100644
--- a/core/res/res/drawable/spinner_background_holo_dark.xml
+++ b/core/res/res/drawable/spinner_background_holo_dark.xml
@@ -14,7 +14,8 @@
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="true">
<item android:state_enabled="false"
android:drawable="@drawable/spinner_disabled_holo_dark" />
<item android:state_pressed="true"
diff --git a/core/res/res/drawable/spinner_background_holo_light.xml b/core/res/res/drawable/spinner_background_holo_light.xml
index 9d17ed0c..899633c 100644
--- a/core/res/res/drawable/spinner_background_holo_light.xml
+++ b/core/res/res/drawable/spinner_background_holo_light.xml
@@ -14,7 +14,8 @@
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:autoMirrored="true">
<item android:state_enabled="false"
android:drawable="@drawable/spinner_disabled_holo_light" />
<item android:state_pressed="true"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 400e892..4116998 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -123,12 +123,8 @@
of them. This should not normally be modified. -->
<bool name="config_closeDialogWhenTouchOutside">true</bool>
- <!-- Device configuration indicating this is a device with limited RAM, so heavier-weight
- features should be turned off. -->
- <bool name="config_lowRamDevice">false</bool>
-
<!-- Device configuration indicating whether we should avoid using accelerated graphics
- in certain places to reduce RAM footprint. This is ignored if config_lowRamDevice
+ in certain places to reduce RAM footprint. This is ignored if ro.config.low_ram
is true (in that case this is assumed true as well). It can allow you to tune down
your device's memory use without going to the point of causing applications to turn
off features. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e497c85..5f5564c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1128,6 +1128,11 @@
<string name="permdesc_anyCodecForPlayback">Allows the app to use any installed
media decoder to decode for playback.</string>
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. This permission allows the app to install or uninstall trusted credentials, a.k.a. CA certificates. [CHAR LIMIT=NONE] -->
+ <string name="permlab_manageCaCertificates">manage trusted credentials</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. [CHAR LIMIT=NONE]-->
+ <string name="permdesc_manageCaCertificates">Allows the app to install and uninstall CA certificates as trusted credentials.</string>
+
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_diagnostic">read/write to resources owned by diag</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ca93d1c..076922f 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -252,7 +252,6 @@
<java-symbol type="bool" name="config_duplicate_port_omadm_wappush" />
<java-symbol type="bool" name="config_enable_emergency_call_while_sim_locked" />
<java-symbol type="bool" name="config_enable_puk_unlock_screen" />
- <java-symbol type="bool" name="config_lowRamDevice" />
<java-symbol type="bool" name="config_mms_content_disposition_support" />
<java-symbol type="bool" name="config_showMenuShortcutsWhenKeyboardPresent" />
<java-symbol type="bool" name="config_sip_wifi_only" />
diff --git a/docs/html/samples/samples_toc.cs b/docs/html/samples/samples_toc.cs
index 8fcd676..14a5b0a 100644
--- a/docs/html/samples/samples_toc.cs
+++ b/docs/html/samples/samples_toc.cs
@@ -1,5 +1,5 @@
-<ul id="nav">
+<ul id="nav" class="samples-nav">
<script type="text/javascript">
<!--
@@ -8,16 +8,9 @@
//-->
</script>
<li class="nav-section">
- <div class="nav-section-header"><a href="/samples/index.html">
+ <div class="nav-section-header empty"><a href="/samples/index.html">
<span class="en">About the Samples</span></a>
</div>
-
</li>
-
- <li id="samples-tree-list" class="nav-section">
- <div class="nav-section-header"><a href="/samples/index.html">
- <span class="en">Root Node (remove)</span></a>
- </div>
- </li>
- </ul>
+</ul>
diff --git a/docs/html/tools/revisions/platforms.jd b/docs/html/tools/revisions/platforms.jd
index 820edbd..7cf3735 100644
--- a/docs/html/tools/revisions/platforms.jd
+++ b/docs/html/tools/revisions/platforms.jd
@@ -22,20 +22,31 @@
-<p>To develop an Android app, you must install at least one Android platform from the SDK Manager
-against which you can compile your app. Often, any given version of the Android will be revised
-with bug fixes or other changes, as denoted by the revision number. Below, you'll find the
-release notes for each version of the platform and the subsequent revisions to the platform
-version.</p>
+<p>This document provides information about Android platform releases. In order to compile your
+application against a particular platform release, you must download and install the SDK Platform
+for that release. If you want to test your application on an emulator, you must also download at
+least one system image for that platform release.</p>
-<p>To determine what revision of an Android platform you have installed, refer to the
-<strong>Installed Packages</strong> listing in the Android
+<p>Each platform release includes system images that support a specific processor architecture,
+such as ARM EABI, Intel x86 or MIPS. Platform releases also include a system image that contains
+Google APIs. The <a href="{@docRoot}tools/help/sdk-manager.html">SDK Manager</a> lists available
+platform system images under each platform version header, for example:</p>
+
+<ul>
+ <li>ARM EABI v7a System Image</li>
+ <li>Intel x86 Atom System Image</li>
+ <li>MIPS System Image</li>
+ <li>Google APIs</li>
+</ul>
+
+<p>To determine what revisions of an Android platform you have installed, refer to the
+<em>Packages</em> listing in the Android
<a href="{@docRoot}tools/help/sdk-manager.html">SDK Manager</a>.</p>
<p class="caution"><strong>Important:</strong> To download the most recent Android
-system components from the Android SDK Manager, you must first update the SDK Tools to
-revision 22 or later and restart the SDK Manager. If you do not,
-the latest Android system components will not be available for download.</p>
+system components from the Android SDK Manager, you must first update the SDK Tools to the
+most recent release and restart the SDK Manager. If you do not, the latest Android system
+components will not be available for download.</p>
@@ -46,6 +57,25 @@
<p><a href="#" onclick="return toggleContent(this)">
<img src="{@docRoot}assets/images/triangle-opened.png"
+class="toggle-content-img" alt="" />Revision 2</a> <em>(August 2013)</em>
+ </p>
+
+ <div class="toggle-content-toggleme">
+
+ <p>Maintenance update. The system version is 4.3.</p>
+ <dl>
+ <dt>Dependencies:</dt>
+ <dd>Android SDK Platform-tools r18 or higher is required.</dd>
+ <dd>Android SDK Tools 22.0.4 or higher is recommended.</dd>
+ </dl>
+
+ </div>
+</div>
+
+<div class="toggle-content closed">
+
+ <p><a href="#" onclick="return toggleContent(this)">
+ <img src="{@docRoot}assets/images/triangle-closed.png"
class="toggle-content-img" alt="" />Revision 1</a> <em>(July 2013)</em>
</p>
@@ -61,6 +91,39 @@
</div>
</div>
+<h3 id="">Google APIs System Image</h3>
+
+<div class="toggle-content opened">
+ <p><a href="#" onclick="return toggleContent(this)">
+ <img src="{@docRoot}assets/images/triangle-opened.png"
+class="toggle-content-img" alt="" />Revision 2</a> <em>(August 2013)</em>
+ </p>
+
+ <div class="toggle-content-toggleme">
+
+ <p>Maintenance update. This release includes
+ <a href="{@docRoot}google/play-services/index.html">Google Play Services</a> version 3.2.25,
+ allowing you to test your application in an emulator using the latest Google Play Services.</p>
+
+ </div>
+</div>
+
+<div class="toggle-content closed">
+ <p><a href="#" onclick="return toggleContent(this)">
+ <img src="{@docRoot}assets/images/triangle-closed.png"
+class="toggle-content-img" alt="" />Revision 1</a> <em>(July 2013)</em>
+ </p>
+
+ <div class="toggle-content-toggleme">
+
+ <p>Initial release. This release includes
+ <a href="{@docRoot}google/play-services/index.html">Google Play Services</a> version 3.1.58.</p>
+
+ </div>
+</div>
+
+
+
<h2 id="4.2">Android 4.2</h2>
diff --git a/graphics/java/android/renderscript/Type.java b/graphics/java/android/renderscript/Type.java
index ef08c29..e023739 100644
--- a/graphics/java/android/renderscript/Type.java
+++ b/graphics/java/android/renderscript/Type.java
@@ -37,10 +37,11 @@
* faces. LOD and cube map faces are booleans to indicate present or not
* present. </p>
*
- * <p>A Type also supports YUV format information to support an {@link
- * android.renderscript.Allocation} in a YUV format. The YUV formats supported
- * are {@link android.graphics.ImageFormat#YV12} and {@link
- * android.graphics.ImageFormat#NV21}.</p>
+ * <p>A Type also supports YUV format information to support an
+ * {@link android.renderscript.Allocation} in a YUV format. The YUV formats
+ * supported are {@link android.graphics.ImageFormat#YV12},
+ * {@link android.graphics.ImageFormat#NV21}, and
+ * {@link android.graphics.ImageFormat#YUV_420_888}</p>
*
* <div class="special reference">
* <h3>Developer Guides</h3>
@@ -284,16 +285,19 @@
/**
* Set the YUV layout for a Type.
*
- * @param yuvFormat {@link android.graphics.ImageFormat#YV12} or {@link android.graphics.ImageFormat#NV21}
+ * @param yuvFormat {@link android.graphics.ImageFormat#YV12}, {@link android.graphics.ImageFormat#NV21}, or
+ * {@link android.graphics.ImageFormat#YUV_420_888}.
*/
public Builder setYuvFormat(int yuvFormat) {
switch (yuvFormat) {
case android.graphics.ImageFormat.NV21:
case android.graphics.ImageFormat.YV12:
+ case android.graphics.ImageFormat.YUV_420_888:
break;
default:
- throw new RSIllegalArgumentException("Only NV21 and YV12 are supported..");
+ throw new RSIllegalArgumentException(
+ "Only ImageFormat.NV21, .YV12, and .YUV_420_888 are supported..");
}
mYuv = yuvFormat;
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index 328ac5d..9ea325a 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -443,7 +443,10 @@
}
@Override public void onServiceDisconnected(ComponentName name) {}
};
- boolean isBound = context.bindService(new Intent(IKeyChainService.class.getName()),
+ Intent intent = new Intent(IKeyChainService.class.getName());
+ ComponentName comp = intent.resolveSystemService(context.getPackageManager(), 0);
+ intent.setComponent(comp);
+ boolean isBound = context.bindService(intent,
keyChainServiceConnection,
Context.BIND_AUTO_CREATE);
if (!isBound) {
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index cb6bb2e..22b1485 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -737,7 +737,7 @@
}
mRsElement = RSC::Element::A_8(mRs);
- mRsScript = new RSC::ScriptIntrinsicBlur(mRs, mRsElement);
+ mRsScript = RSC::ScriptIntrinsicBlur::create(mRs, mRsElement);
}
RSC::sp<const RSC::Type> t = RSC::Type::create(mRs, mRsElement, width, height, 0);
@@ -749,7 +749,8 @@
outImage);
mRsScript->setRadius(radius);
- mRsScript->blur(ain, aout);
+ mRsScript->setInput(ain);
+ mRsScript->forEach(aout);
// replace the original image's pointer, avoiding a copy back to the original buffer
free(*image);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index ef02cfdb..be83581 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1316,19 +1316,6 @@
}
/**
- * @hide
- * Signals whether remote submix audio rerouting is enabled.
- */
- public void setRemoteSubmixOn(boolean on, int address) {
- IAudioService service = getService();
- try {
- service.setRemoteSubmixOn(on, address);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in setRemoteSubmixOn", e);
- }
- }
-
- /**
* Sets audio routing to the wired headset on or off.
*
* @param on set <var>true</var> to route audio to/from wired
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 470c571..3478007 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -140,24 +140,22 @@
private static final int MSG_PERSIST_MASTER_VOLUME = 2;
private static final int MSG_PERSIST_RINGER_MODE = 3;
private static final int MSG_MEDIA_SERVER_DIED = 4;
- private static final int MSG_MEDIA_SERVER_STARTED = 5;
- private static final int MSG_PLAY_SOUND_EFFECT = 6;
- private static final int MSG_BTA2DP_DOCK_TIMEOUT = 7;
- private static final int MSG_LOAD_SOUND_EFFECTS = 8;
- private static final int MSG_SET_FORCE_USE = 9;
- private static final int MSG_BT_HEADSET_CNCT_FAILED = 10;
- private static final int MSG_SET_ALL_VOLUMES = 11;
- private static final int MSG_PERSIST_MASTER_VOLUME_MUTE = 12;
- private static final int MSG_REPORT_NEW_ROUTES = 13;
- private static final int MSG_SET_FORCE_BT_A2DP_USE = 14;
- private static final int MSG_SET_RSX_CONNECTION_STATE = 15; // change remote submix connection
- private static final int MSG_CHECK_MUSIC_ACTIVE = 16;
- private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 17;
- private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 18;
- private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 19;
- private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 20;
- private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 21;
- private static final int MSG_UNLOAD_SOUND_EFFECTS = 22;
+ private static final int MSG_PLAY_SOUND_EFFECT = 5;
+ private static final int MSG_BTA2DP_DOCK_TIMEOUT = 6;
+ private static final int MSG_LOAD_SOUND_EFFECTS = 7;
+ private static final int MSG_SET_FORCE_USE = 8;
+ private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
+ private static final int MSG_SET_ALL_VOLUMES = 10;
+ private static final int MSG_PERSIST_MASTER_VOLUME_MUTE = 11;
+ private static final int MSG_REPORT_NEW_ROUTES = 12;
+ private static final int MSG_SET_FORCE_BT_A2DP_USE = 13;
+ private static final int MSG_CHECK_MUSIC_ACTIVE = 14;
+ private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 15;
+ private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 16;
+ private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 17;
+ private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 18;
+ private static final int MSG_BROADCAST_BT_CONNECTION_STATE = 19;
+ private static final int MSG_UNLOAD_SOUND_EFFECTS = 20;
// start of messages handled under wakelock
// these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
// and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
@@ -181,8 +179,6 @@
// protects mRingerMode
private final Object mSettingsLock = new Object();
- private boolean mMediaServerOk;
-
private SoundPool mSoundPool;
private final Object mSoundEffectsLock = new Object();
private static final int NUM_SOUNDPOOL_CHANNELS = 4;
@@ -287,23 +283,13 @@
public void onError(int error) {
switch (error) {
case AudioSystem.AUDIO_STATUS_SERVER_DIED:
- if (mMediaServerOk) {
- sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED, SENDMSG_NOOP, 0, 0,
- null, 1500);
- mMediaServerOk = false;
- }
- break;
- case AudioSystem.AUDIO_STATUS_OK:
- if (!mMediaServerOk) {
- sendMsg(mAudioHandler, MSG_MEDIA_SERVER_STARTED, SENDMSG_NOOP, 0, 0,
- null, 0);
- mMediaServerOk = true;
- }
+ sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED,
+ SENDMSG_NOOP, 0, 0, null, 0);
break;
default:
break;
}
- }
+ }
};
/**
@@ -500,6 +486,8 @@
mMediaFocusControl = new MediaFocusControl(mAudioHandler.getLooper(),
mContext, /*VolumeController*/ mVolumePanel, this);
+ AudioSystem.setErrorCallback(mAudioSystemCallback);
+
boolean cameraSoundForced = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_camera_sound_forced);
mCameraSoundForced = new Boolean(cameraSoundForced);
@@ -529,15 +517,12 @@
createStreamStates();
readAndSetLowRamDevice();
- mMediaServerOk = true;
// Call setRingerModeInt() to apply correct mute
// state on streams affected by ringer mode.
mRingerModeMutedStreams = 0;
setRingerModeInt(getRingerMode(), false);
- AudioSystem.setErrorCallback(mAudioSystemCallback);
-
// Register for device connection intent broadcasts.
IntentFilter intentFilter =
new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
@@ -2393,26 +2378,6 @@
}
};
- /** see AudioManager.setRemoteSubmixOn(boolean on) */
- public void setRemoteSubmixOn(boolean on, int address) {
- sendMsg(mAudioHandler, MSG_SET_RSX_CONNECTION_STATE,
- SENDMSG_REPLACE /* replace with QUEUE when multiple addresses are supported */,
- on ? 1 : 0 /*arg1*/,
- address /*arg2*/,
- null/*obj*/, 0/*delay*/);
- }
-
- private void onSetRsxConnectionState(int available, int address) {
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_REMOTE_SUBMIX,
- available == 1 ?
- AudioSystem.DEVICE_STATE_AVAILABLE : AudioSystem.DEVICE_STATE_UNAVAILABLE,
- String.valueOf(address) /*device_address*/);
- AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX,
- available == 1 ?
- AudioSystem.DEVICE_STATE_AVAILABLE : AudioSystem.DEVICE_STATE_UNAVAILABLE,
- String.valueOf(address) /*device_address*/);
- }
-
private void onCheckMusicActive() {
synchronized (mSafeMediaVolumeState) {
if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
@@ -3453,21 +3418,17 @@
break;
case MSG_MEDIA_SERVER_DIED:
- if (!mMediaServerOk) {
+ if (AudioSystem.checkAudioFlinger() != AudioSystem.AUDIO_STATUS_OK) {
Log.e(TAG, "Media server died.");
- // Force creation of new IAudioFlinger interface so that we are notified
- // when new media_server process is back to life.
- AudioSystem.setErrorCallback(mAudioSystemCallback);
sendMsg(mAudioHandler, MSG_MEDIA_SERVER_DIED, SENDMSG_NOOP, 0, 0,
null, 500);
+ break;
}
- break;
-
- case MSG_MEDIA_SERVER_STARTED:
Log.e(TAG, "Media server started.");
+
// indicate to audio HAL that we start the reconfiguration phase after a media
// server crash
- // Note that MSG_MEDIA_SERVER_STARTED message is only received when the media server
+ // Note that we only execute this when the media server
// process restarts after a crash, not the first time it is started.
AudioSystem.setParameters("restarting=true");
@@ -3600,10 +3561,6 @@
break;
}
- case MSG_SET_RSX_CONNECTION_STATE:
- onSetRsxConnectionState(msg.arg1/*available*/, msg.arg2/*address*/);
- break;
-
case MSG_CHECK_MUSIC_ACTIVE:
onCheckMusicActive();
break;
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 4805da5..661b0fd 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -177,12 +177,10 @@
{
synchronized (AudioSystem.class) {
mErrorCallback = cb;
+ if (cb != null) {
+ cb.onError(checkAudioFlinger());
+ }
}
- // Calling a method on AudioFlinger here makes sure that we bind to IAudioFlinger
- // binder interface death. Not doing that would result in not being notified of
- // media_server process death if no other method is called on AudioSystem that reaches
- // to AudioFlinger.
- isMicrophoneMuted();
}
private static void errorCallbackFromNative(int error)
@@ -404,5 +402,5 @@
public static native int getOutputLatency(int stream);
public static native int setLowRamDevice(boolean isLowRamDevice);
-
+ public static native int checkAudioFlinger();
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 903927b..d8ce36a 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -114,8 +114,6 @@
boolean isBluetoothA2dpOn();
- oneway void setRemoteSubmixOn(boolean on, int address);
-
int requestAudioFocus(int mainStreamType, int durationHint, IBinder cb,
IAudioFocusDispatcher fd, String clientId, String callingPackageName);
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 241c7fac..946dd71 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -38,6 +38,8 @@
import android.graphics.Bitmap;
import android.graphics.SurfaceTexture;
import android.media.AudioManager;
+import android.media.MediaFormat;
+import android.media.SubtitleData;
import java.io.File;
import java.io.FileDescriptor;
@@ -1335,6 +1337,7 @@
mOnInfoListener = null;
mOnVideoSizeChangedListener = null;
mOnTimedTextListener = null;
+ mOnSubtitleDataListener = null;
_release();
}
@@ -1526,20 +1529,43 @@
* ISO-639-2 language code, "und", is returned.
*/
public String getLanguage() {
- return mLanguage;
+ String language = mFormat.getString(MediaFormat.KEY_LANGUAGE);
+ return language == null ? "und" : language;
+ }
+
+ /**
+ * Gets the {@link MediaFormat} of the track. If the format is
+ * unknown or could not be determined, null is returned.
+ */
+ public MediaFormat getFormat() {
+ if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT) {
+ return mFormat;
+ }
+ return null;
}
public static final int MEDIA_TRACK_TYPE_UNKNOWN = 0;
public static final int MEDIA_TRACK_TYPE_VIDEO = 1;
public static final int MEDIA_TRACK_TYPE_AUDIO = 2;
public static final int MEDIA_TRACK_TYPE_TIMEDTEXT = 3;
+ /** @hide */
+ public static final int MEDIA_TRACK_TYPE_SUBTITLE = 4;
final int mTrackType;
- final String mLanguage;
+ final MediaFormat mFormat;
TrackInfo(Parcel in) {
mTrackType = in.readInt();
- mLanguage = in.readString();
+ // TODO: parcel in the full MediaFormat
+ String language = in.readString();
+
+ if (mTrackType == MEDIA_TRACK_TYPE_TIMEDTEXT) {
+ mFormat = MediaFormat.createSubtitleFormat(
+ MEDIA_MIMETYPE_TEXT_SUBRIP, language);
+ } else {
+ mFormat = new MediaFormat();
+ mFormat.setString(MediaFormat.KEY_LANGUAGE, language);
+ }
}
/**
@@ -1556,7 +1582,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mTrackType);
- dest.writeString(mLanguage);
+ dest.writeString(getLanguage());
}
/**
@@ -1891,6 +1917,7 @@
private static final int MEDIA_TIMED_TEXT = 99;
private static final int MEDIA_ERROR = 100;
private static final int MEDIA_INFO = 200;
+ private static final int MEDIA_SUBTITLE_DATA = 201;
private class EventHandler extends Handler
{
@@ -1970,6 +1997,18 @@
}
return;
+ case MEDIA_SUBTITLE_DATA:
+ if (mOnSubtitleDataListener == null) {
+ return;
+ }
+ if (msg.obj instanceof Parcel) {
+ Parcel parcel = (Parcel) msg.obj;
+ SubtitleData data = new SubtitleData(parcel);
+ parcel.recycle();
+ mOnSubtitleDataListener.onSubtitleData(mMediaPlayer, data);
+ }
+ return;
+
case MEDIA_NOP: // interface test message - ignore
break;
@@ -2181,6 +2220,30 @@
private OnTimedTextListener mOnTimedTextListener;
+ /**
+ * Interface definition of a callback to be invoked when a
+ * track has data available.
+ *
+ * @hide
+ */
+ public interface OnSubtitleDataListener
+ {
+ public void onSubtitleData(MediaPlayer mp, SubtitleData data);
+ }
+
+ /**
+ * Register a callback to be invoked when a track has data available.
+ *
+ * @param listener the callback that will be run
+ *
+ * @hide
+ */
+ public void setOnSubtitleDataListener(OnSubtitleDataListener listener)
+ {
+ mOnSubtitleDataListener = listener;
+ }
+
+ private OnSubtitleDataListener mOnSubtitleDataListener;
/* Do not change these values without updating their counterparts
* in include/media/mediaplayer.h!
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 3e688db..1d2b889 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -179,6 +179,27 @@
* is applied.
*/
public static final int VOICE_COMMUNICATION = 7;
+
+ /**
+ * Audio source for a submix of audio streams to be presented remotely.
+ * <p>
+ * An application can use this audio source to capture a mix of audio streams
+ * that should be transmitted to a remote receiver such as a Wifi display.
+ * While recording is active, these audio streams are redirected to the remote
+ * submix instead of being played on the device speaker or headset.
+ * </p><p>
+ * Certain streams are excluded from the remote submix, including
+ * {@link AudioManager#STREAM_RING}, {@link AudioManager#STREAM_ALARM},
+ * and {@link AudioManager#STREAM_NOTIFICATION}. These streams will continue
+ * to be presented locally as usual.
+ * </p><p>
+ * Capturing the remote submix audio requires the
+ * {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT} permission.
+ * This permission is reserved for use by system components and is not available to
+ * third-party applications.
+ * </p>
+ */
+ public static final int REMOTE_SUBMIX = 8;
}
/**
@@ -294,7 +315,7 @@
* @see android.media.MediaRecorder.AudioSource
*/
public static final int getAudioSourceMax() {
- return AudioSource.VOICE_COMMUNICATION;
+ return AudioSource.REMOTE_SUBMIX;
}
/**
diff --git a/media/java/android/media/SubtitleData.java b/media/java/android/media/SubtitleData.java
new file mode 100644
index 0000000..f552e82
--- /dev/null
+++ b/media/java/android/media/SubtitleData.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2011 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 android.media;
+
+import android.os.Parcel;
+import android.util.Log;
+
+/**
+ * @hide
+ *
+ * Class to hold the subtitle track's data, including:
+ * <ul>
+ * <li> Track index</li>
+ * <li> Start time (in microseconds) of the data</li>
+ * <li> Duration (in microseconds) of the data</li>
+ * <li> A byte-array of the data</li>
+ * </ul>
+ *
+ * <p> To receive the subtitle data, applications need to do the following:
+ *
+ * <ul>
+ * <li> Select a track of type MEDIA_TRACK_TYPE_SUBTITLE with {@link MediaPlayer.selectTrack(int)</li>
+ * <li> Implement the {@link MediaPlayer.OnSubtitleDataListener} interface</li>
+ * <li> Register the {@link MediaPlayer.OnSubtitleDataListener} callback on a MediaPlayer object</li>
+ * </ul>
+ *
+ * @see android.media.MediaPlayer
+ */
+public final class SubtitleData
+{
+ private static final String TAG = "SubtitleData";
+
+ private int mTrackIndex;
+ private long mStartTimeUs;
+ private long mDurationUs;
+ private byte[] mData;
+
+ public SubtitleData(Parcel parcel) {
+ if (!parseParcel(parcel)) {
+ throw new IllegalArgumentException("parseParcel() fails");
+ }
+ }
+
+ public int getTrackIndex() {
+ return mTrackIndex;
+ }
+
+ public long getStartTimeUs() {
+ return mStartTimeUs;
+ }
+
+ public long getDurationUs() {
+ return mDurationUs;
+ }
+
+ public byte[] getData() {
+ return mData;
+ }
+
+ private boolean parseParcel(Parcel parcel) {
+ parcel.setDataPosition(0);
+ if (parcel.dataAvail() == 0) {
+ return false;
+ }
+
+ mTrackIndex = parcel.readInt();
+ mStartTimeUs = parcel.readLong();
+ mDurationUs = parcel.readLong();
+ mData = new byte[parcel.readInt()];
+ parcel.readByteArray(mData);
+
+ return true;
+ }
+}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 5225e23..722087c 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -18,7 +18,7 @@
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraMetadata;
-import android.hardware.camera2.CameraPropertiesKeys;
+import android.hardware.camera2.CameraProperties;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.ICameraDeviceCallbacks;
import android.hardware.camera2.ICameraDeviceUser;
@@ -271,7 +271,7 @@
assertEquals(CameraBinderTestUtils.NO_ERROR, status);
assertFalse(info.isEmpty());
- assertNotNull(info.get(CameraPropertiesKeys.Scaler.AVAILABLE_FORMATS));
+ assertNotNull(info.get(CameraProperties.SCALER_AVAILABLE_FORMATS));
}
@SmallTest
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
index 074bfe48..2d26ac7 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
@@ -86,9 +86,9 @@
private Writer mProcMemWriter;
private Writer mMemWriter;
- private CamcorderProfile mCamcorderProfile = CamcorderProfile.get(CAMERA_ID);
- private int mVideoWidth = mCamcorderProfile.videoFrameWidth;
- private int mVideoHeight = mCamcorderProfile.videoFrameHeight;
+ private CamcorderProfile mCamcorderProfile;
+ private int mVideoWidth;
+ private int mVideoHeight;
Camera mCamera;
@@ -99,6 +99,12 @@
@Override
protected void setUp() throws Exception {
super.setUp();
+ //Check if the device support the camcorder
+ CamcorderProfile mCamcorderProfile = CamcorderProfile.get(CAMERA_ID);
+ if (mCamcorderProfile != null) {
+ mVideoWidth = mCamcorderProfile.videoFrameWidth;
+ mVideoHeight = mCamcorderProfile.videoFrameHeight;
+ }
//Insert a 2 second before launching the test activity. This is
//the workaround for the race condition of requesting the updated surface.
Thread.sleep(2000);
@@ -332,7 +338,7 @@
// USER PID PPID VSIZE RSS WCHAN PC NAME
// media 131 1 13676 4796 ffffffff 400b1bd0 S media.log
// media 219 131 37768 6892 ffffffff 400b236c S /system/bin/mediaserver
- String memusage = poList[2].concat("\n");
+ String memusage = poList[poList.length-1].concat("\n");
return memusage;
}
@@ -410,59 +416,65 @@
// Test case 4: Capture the memory usage after every 20 video only recorded
@LargeTest
public void testH263RecordVideoOnlyMemoryUsage() throws Exception {
- boolean memoryResult = false;
- mStartPid = getMediaserverPid();
- int frameRate = MediaProfileReader.getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H263);
- assertTrue("H263 video recording frame rate", frameRate != -1);
- for (int i = 0; i < NUM_STRESS_LOOP; i++) {
- assertTrue(stressVideoRecord(frameRate, mVideoWidth, mVideoHeight,
- MediaRecorder.VideoEncoder.H263, MediaRecorder.OutputFormat.MPEG_4,
- MediaNames.RECORDED_VIDEO_3GP, true));
- getMemoryWriteToLog(i);
- writeProcmemInfo();
+ if (mCamcorderProfile != null) {
+ boolean memoryResult = false;
+ mStartPid = getMediaserverPid();
+ int frameRate = MediaProfileReader
+ .getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H263);
+ assertTrue("H263 video recording frame rate", frameRate != -1);
+ for (int i = 0; i < NUM_STRESS_LOOP; i++) {
+ assertTrue(stressVideoRecord(frameRate, mVideoWidth, mVideoHeight,
+ MediaRecorder.VideoEncoder.H263, MediaRecorder.OutputFormat.MPEG_4,
+ MediaNames.RECORDED_VIDEO_3GP, true));
+ getMemoryWriteToLog(i);
+ writeProcmemInfo();
+ }
+ memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT);
+ assertTrue("H263 record only memory test", memoryResult);
}
- memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT);
- assertTrue("H263 record only memory test", memoryResult);
}
// Test case 5: Capture the memory usage after every 20 video only recorded
@LargeTest
public void testMpeg4RecordVideoOnlyMemoryUsage() throws Exception {
- boolean memoryResult = false;
-
- mStartPid = getMediaserverPid();
- int frameRate = MediaProfileReader.getMaxFrameRateForCodec
- (MediaRecorder.VideoEncoder.MPEG_4_SP);
- assertTrue("MPEG4 video recording frame rate", frameRate != -1);
- for (int i = 0; i < NUM_STRESS_LOOP; i++) {
- assertTrue(stressVideoRecord(frameRate, mVideoWidth, mVideoHeight,
- MediaRecorder.VideoEncoder.MPEG_4_SP, MediaRecorder.OutputFormat.MPEG_4,
- MediaNames.RECORDED_VIDEO_3GP, true));
- getMemoryWriteToLog(i);
- writeProcmemInfo();
+ if (mCamcorderProfile != null) {
+ boolean memoryResult = false;
+ mStartPid = getMediaserverPid();
+ int frameRate = MediaProfileReader.getMaxFrameRateForCodec
+ (MediaRecorder.VideoEncoder.MPEG_4_SP);
+ assertTrue("MPEG4 video recording frame rate", frameRate != -1);
+ for (int i = 0; i < NUM_STRESS_LOOP; i++) {
+ assertTrue(stressVideoRecord(frameRate, mVideoWidth, mVideoHeight,
+ MediaRecorder.VideoEncoder.MPEG_4_SP, MediaRecorder.OutputFormat.MPEG_4,
+ MediaNames.RECORDED_VIDEO_3GP, true));
+ getMemoryWriteToLog(i);
+ writeProcmemInfo();
+ }
+ memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT);
+ assertTrue("mpeg4 record only memory test", memoryResult);
}
- memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT);
- assertTrue("mpeg4 record only memory test", memoryResult);
}
// Test case 6: Capture the memory usage after every 20 video and audio
// recorded
@LargeTest
public void testRecordVideoAudioMemoryUsage() throws Exception {
- boolean memoryResult = false;
-
- mStartPid = getMediaserverPid();
- int frameRate = MediaProfileReader.getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H263);
- assertTrue("H263 video recording frame rate", frameRate != -1);
- for (int i = 0; i < NUM_STRESS_LOOP; i++) {
- assertTrue(stressVideoRecord(frameRate, mVideoWidth, mVideoHeight,
- MediaRecorder.VideoEncoder.H263, MediaRecorder.OutputFormat.MPEG_4,
- MediaNames.RECORDED_VIDEO_3GP, false));
- getMemoryWriteToLog(i);
- writeProcmemInfo();
+ if (mCamcorderProfile != null) {
+ boolean memoryResult = false;
+ mStartPid = getMediaserverPid();
+ int frameRate = MediaProfileReader
+ .getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H263);
+ assertTrue("H263 video recording frame rate", frameRate != -1);
+ for (int i = 0; i < NUM_STRESS_LOOP; i++) {
+ assertTrue(stressVideoRecord(frameRate, mVideoWidth, mVideoHeight,
+ MediaRecorder.VideoEncoder.H263, MediaRecorder.OutputFormat.MPEG_4,
+ MediaNames.RECORDED_VIDEO_3GP, false));
+ getMemoryWriteToLog(i);
+ writeProcmemInfo();
+ }
+ memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT);
+ assertTrue("H263 audio video record memory test", memoryResult);
}
- memoryResult = validateMemoryResult(mStartPid, mStartMemory, ENCODER_LIMIT);
- assertTrue("H263 audio video record memory test", memoryResult);
}
// Test case 7: Capture the memory usage after every 20 audio only recorded
diff --git a/packages/Keyguard/AndroidManifest.xml b/packages/Keyguard/AndroidManifest.xml
index 7d77c48..f3106da 100644
--- a/packages/Keyguard/AndroidManifest.xml
+++ b/packages/Keyguard/AndroidManifest.xml
@@ -38,6 +38,9 @@
<uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" />
<uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
+ <!-- Permission for the Hotword detector service -->
+ <uses-permission android:name="com.google.android.googlequicksearchbox.SEARCH_API" />
+
<application android:label="@string/app_name"
android:process="com.android.systemui"
android:persistent="true" >
diff --git a/packages/Keyguard/src/com/android/keyguard/HotwordServiceClient.java b/packages/Keyguard/src/com/android/keyguard/HotwordServiceClient.java
new file mode 100644
index 0000000..94733d4
--- /dev/null
+++ b/packages/Keyguard/src/com/android/keyguard/HotwordServiceClient.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2013 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.keyguard;
+
+import android.app.SearchManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.google.android.search.service.IHotwordService;
+import com.google.android.search.service.IHotwordServiceCallback;
+
+/**
+ * Utility class with its callbacks to simplify usage of {@link IHotwordService}.
+ *
+ * The client is meant to be used for a single hotword detection in a session.
+ * start() -> stop(); client is asked to stop & disconnect from the service.
+ * start() -> onHotwordDetected(); client disconnects from the service automatically.
+ */
+public class HotwordServiceClient implements Handler.Callback {
+ private static final String TAG = "HotwordServiceClient";
+ private static final boolean DBG = true;
+ private static final String ACTION_HOTWORD =
+ "com.google.android.search.service.IHotwordService";
+
+ private static final int MSG_SERVICE_CONNECTED = 0;
+ private static final int MSG_SERVICE_DISCONNECTED = 1;
+ private static final int MSG_HOTWORD_STARTED = 2;
+ private static final int MSG_HOTWORD_STOPPED = 3;
+ private static final int MSG_HOTWORD_DETECTED = 4;
+
+ private final Context mContext;
+ private final Callback mClientCallback;
+ private final Handler mHandler;
+
+ private IHotwordService mService;
+
+ public HotwordServiceClient(Context context, Callback callback) {
+ mContext = context;
+ mClientCallback = callback;
+ mHandler = new Handler(this);
+ }
+
+ public interface Callback {
+ void onServiceConnected();
+ void onServiceDisconnected();
+ void onHotwordDetectionStarted();
+ void onHotwordDetectionStopped();
+ void onHotwordDetected(String action);
+ }
+
+ /**
+ * Binds to the {@link IHotwordService} and starts hotword detection
+ * when the service is connected.
+ *
+ * @return false if the service can't be bound to.
+ */
+ public synchronized boolean start() {
+ if (mService != null) {
+ if (DBG) Log.d(TAG, "Multiple call to start(), service was already bound");
+ return true;
+ } else {
+ // TODO: The hotword service is currently hosted within the search app
+ // so the component handling the assist intent should handle hotwording
+ // as well.
+ final Intent intent =
+ ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE))
+ .getAssistIntent(mContext, true, UserHandle.USER_CURRENT);
+ if (intent == null) {
+ return false;
+ }
+
+ Intent hotwordIntent = new Intent(ACTION_HOTWORD);
+ hotwordIntent.fillIn(intent, Intent.FILL_IN_PACKAGE);
+ return mContext.bindService(
+ hotwordIntent,
+ mConnection,
+ Context.BIND_AUTO_CREATE);
+ }
+ }
+
+ /**
+ * Unbinds from the the {@link IHotwordService}.
+ */
+ public synchronized void stop() {
+ if (mService != null) {
+ mContext.unbindService(mConnection);
+ mService = null;
+ }
+ }
+
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SERVICE_CONNECTED:
+ handleServiceConnected();
+ break;
+ case MSG_SERVICE_DISCONNECTED:
+ handleServiceDisconnected();
+ break;
+ case MSG_HOTWORD_STARTED:
+ handleHotwordDetectionStarted();
+ break;
+ case MSG_HOTWORD_STOPPED:
+ handleHotwordDetectionStopped();
+ break;
+ case MSG_HOTWORD_DETECTED:
+ handleHotwordDetected((String) msg.obj);
+ break;
+ default:
+ if (DBG) Log.e(TAG, "Unhandled message");
+ return false;
+ }
+ return true;
+ }
+
+ private void handleServiceConnected() {
+ if (DBG) Log.d(TAG, "handleServiceConnected()");
+ if (mClientCallback != null) mClientCallback.onServiceConnected();
+ try {
+ mService.requestHotwordDetection(mServiceCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception while registering callback", e);
+ mHandler.sendEmptyMessage(MSG_SERVICE_DISCONNECTED);
+ }
+ }
+
+ private void handleServiceDisconnected() {
+ if (DBG) Log.d(TAG, "handleServiceDisconnected()");
+ mService = null;
+ if (mClientCallback != null) mClientCallback.onServiceDisconnected();
+ }
+
+ private void handleHotwordDetectionStarted() {
+ if (DBG) Log.d(TAG, "handleHotwordDetectionStarted()");
+ if (mClientCallback != null) mClientCallback.onHotwordDetectionStarted();
+ }
+
+ private void handleHotwordDetectionStopped() {
+ if (DBG) Log.d(TAG, "handleHotwordDetectionStopped()");
+ if (mClientCallback != null) mClientCallback.onHotwordDetectionStopped();
+ }
+
+ void handleHotwordDetected(final String action) {
+ if (DBG) Log.d(TAG, "handleHotwordDetected()");
+ if (mClientCallback != null) mClientCallback.onHotwordDetected(action);
+ stop();
+ }
+
+ /**
+ * Implements service connection methods.
+ */
+ private ServiceConnection mConnection = new ServiceConnection() {
+ /**
+ * Called when the service connects after calling bind().
+ */
+ public void onServiceConnected(ComponentName className, IBinder iservice) {
+ mService = IHotwordService.Stub.asInterface(iservice);
+ mHandler.sendEmptyMessage(MSG_SERVICE_CONNECTED);
+ }
+
+ /**
+ * Called if the service unexpectedly disconnects. This indicates an error.
+ */
+ public void onServiceDisconnected(ComponentName className) {
+ mService = null;
+ mHandler.sendEmptyMessage(MSG_SERVICE_DISCONNECTED);
+ }
+ };
+
+ /**
+ * Implements the AIDL IHotwordServiceCallback interface.
+ */
+ private final IHotwordServiceCallback mServiceCallback = new IHotwordServiceCallback.Stub() {
+
+ public void onHotwordDetectionStarted() {
+ mHandler.sendEmptyMessage(MSG_HOTWORD_STARTED);
+ }
+
+ public void onHotwordDetectionStopped() {
+ mHandler.sendEmptyMessage(MSG_HOTWORD_STOPPED);
+ }
+
+ public void onHotwordDetected(String action) {
+ mHandler.obtainMessage(MSG_HOTWORD_DETECTED, action).sendToTarget();
+ }
+ };
+}
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSelectorView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSelectorView.java
index 4d891be..1c658e3 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardSelectorView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSelectorView.java
@@ -22,8 +22,11 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.Settings;
+import android.telephony.TelephonyManager;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Slog;
@@ -34,12 +37,15 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.multiwaveview.GlowPadView;
import com.android.internal.widget.multiwaveview.GlowPadView.OnTriggerListener;
+import com.android.keyguard.KeyguardHostView.OnDismissAction;
public class KeyguardSelectorView extends LinearLayout implements KeyguardSecurityView {
private static final boolean DEBUG = KeyguardHostView.DEBUG;
private static final String TAG = "SecuritySelectorView";
private static final String ASSIST_ICON_METADATA_NAME =
"com.android.systemui.action_assist_icon";
+ // Flag to enable/disable hotword detection on lock screen.
+ private static final boolean FLAG_HOTWORD = true;
private KeyguardSecurityCallback mCallback;
private GlowPadView mGlowPadView;
@@ -51,11 +57,15 @@
private LockPatternUtils mLockPatternUtils;
private SecurityMessageDisplay mSecurityMessageDisplay;
private Drawable mBouncerFrame;
+ private HotwordServiceClient mHotwordClient;
OnTriggerListener mOnTriggerListener = new OnTriggerListener() {
public void onTrigger(View v, int target) {
final int resId = mGlowPadView.getResourceIdForTarget(target);
+ if (FLAG_HOTWORD) {
+ maybeStopHotwordDetector();
+ }
switch (resId) {
case R.drawable.ic_action_assist_generic:
Intent assistIntent =
@@ -103,7 +113,7 @@
};
- KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
+ KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
@Override
public void onDevicePolicyManagerStateChanged() {
@@ -114,6 +124,24 @@
public void onSimStateChanged(State simState) {
updateTargets();
}
+
+ @Override
+ public void onPhoneStateChanged(int phoneState) {
+ if (FLAG_HOTWORD) {
+ // We need to stop the hotwording when a phone call comes in
+ // TODO(sansid): This is not really needed if onPause triggers
+ // when we navigate away from the keyguard
+ if (phoneState == TelephonyManager.CALL_STATE_RINGING) {
+ if (DEBUG) Log.d(TAG, "Stopping due to CALL_STATE_RINGING");
+ maybeStopHotwordDetector();
+ }
+ }
+ }
+
+ @Override
+ public void onUserSwitching(int userId) {
+ maybeStopHotwordDetector();
+ }
};
private final KeyguardActivityLauncher mActivityLauncher = new KeyguardActivityLauncher() {
@@ -152,6 +180,9 @@
mSecurityMessageDisplay = new KeyguardMessageArea.Helper(this);
View bouncerFrameView = findViewById(R.id.keyguard_selector_view_frame);
mBouncerFrame = bouncerFrameView.getBackground();
+ if (FLAG_HOTWORD) {
+ mHotwordClient = new HotwordServiceClient(getContext(), mHotwordCallback);
+ }
}
public void setCarrierArea(View carrierArea) {
@@ -254,12 +285,22 @@
@Override
public void onPause() {
- KeyguardUpdateMonitor.getInstance(getContext()).removeCallback(mInfoCallback);
+ KeyguardUpdateMonitor.getInstance(getContext()).removeCallback(mUpdateCallback);
}
@Override
public void onResume(int reason) {
- KeyguardUpdateMonitor.getInstance(getContext()).registerCallback(mInfoCallback);
+ KeyguardUpdateMonitor.getInstance(getContext()).registerCallback(mUpdateCallback);
+ // TODO: Figure out if there's a better way to do it.
+ // Right now we don't get onPause at all, and onResume gets called
+ // multiple times (even when the screen is turned off with VIEW_REVEALED)
+ if (reason == SCREEN_ON) {
+ if (!KeyguardUpdateMonitor.getInstance(getContext()).isSwitchingUser()) {
+ maybeStartHotwordDetector();
+ }
+ } else {
+ maybeStopHotwordDetector();
+ }
}
@Override
@@ -280,4 +321,83 @@
KeyguardSecurityViewHelper.
hideBouncer(mSecurityMessageDisplay, mFadeView, mBouncerFrame, duration);
}
+
+ /**
+ * Start the hotword detector if:
+ * <li> HOTWORDING_ENABLED is true and
+ * <li> HotwordUnlock is initialized and
+ * <li> TelephonyManager is in CALL_STATE_IDLE
+ *
+ * If this method is called when the screen is off,
+ * it attempts to stop hotwording if it's running.
+ */
+ private void maybeStartHotwordDetector() {
+ if (FLAG_HOTWORD) {
+ if (DEBUG) Log.d(TAG, "maybeStartHotwordDetector()");
+ // Don't start it if the screen is off or not showing
+ PowerManager powerManager = (PowerManager) getContext().getSystemService(
+ Context.POWER_SERVICE);
+ if (!powerManager.isScreenOn()) {
+ if (DEBUG) Log.d(TAG, "screen was off, not starting");
+ return;
+ }
+
+ KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(getContext());
+ if (monitor.getPhoneState() != TelephonyManager.CALL_STATE_IDLE) {
+ if (DEBUG) Log.d(TAG, "Call underway, not starting");
+ return;
+ }
+ if (!mHotwordClient.start()) {
+ Log.w(TAG, "Failed to start the hotword detector");
+ }
+ }
+ }
+
+ /**
+ * Stop hotword detector if HOTWORDING_ENABLED is true.
+ */
+ private void maybeStopHotwordDetector() {
+ if (FLAG_HOTWORD) {
+ if (DEBUG) Log.d(TAG, "maybeStopHotwordDetector()");
+ mHotwordClient.stop();
+ }
+ }
+
+ private final HotwordServiceClient.Callback mHotwordCallback =
+ new HotwordServiceClient.Callback() {
+ private static final String TAG = "HotwordServiceClient.Callback";
+
+ @Override
+ public void onServiceConnected() {
+ if (DEBUG) Log.d(TAG, "onServiceConnected()");
+ }
+
+ @Override
+ public void onServiceDisconnected() {
+ if (DEBUG) Log.d(TAG, "onServiceDisconnected()");
+ }
+
+ @Override
+ public void onHotwordDetectionStarted() {
+ if (DEBUG) Log.d(TAG, "onHotwordDetectionStarted()");
+ // TODO: Change the usage of SecurityMessageDisplay to a better visual indication.
+ mSecurityMessageDisplay.setMessage("\"Ok Google...\"", true);
+ }
+
+ @Override
+ public void onHotwordDetectionStopped() {
+ if (DEBUG) Log.d(TAG, "onHotwordDetectionStopped()");
+ // TODO: Change the usage of SecurityMessageDisplay to a better visual indication.
+ }
+
+ @Override
+ public void onHotwordDetected(String action) {
+ if (DEBUG) Log.d(TAG, "onHotwordDetected(" + action + ")");
+ if (action != null) {
+ Intent intent = new Intent(action);
+ mActivityLauncher.launchActivity(intent, true, true, null, null);
+ }
+ mCallback.userActivity(0);
+ }
+ };
}
diff --git a/packages/Keyguard/src/com/google/android/search/service/IHotwordService.aidl b/packages/Keyguard/src/com/google/android/search/service/IHotwordService.aidl
new file mode 100644
index 0000000..e053d7d
--- /dev/null
+++ b/packages/Keyguard/src/com/google/android/search/service/IHotwordService.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 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.google.android.search.service;
+
+import com.google.android.search.service.IHotwordServiceCallback;
+
+/**
+ * Interface exposing hotword detector as a service.
+ */
+oneway interface IHotwordService {
+
+ /**
+ * Indicates a desire to start hotword detection.
+ * It's best-effort and the client should rely on
+ * the callbacks to figure out if hotwording was actually
+ * started or not.
+ *
+ * @param a callback to notify of hotword events.
+ */
+ void requestHotwordDetection(in IHotwordServiceCallback callback);
+}
diff --git a/packages/Keyguard/src/com/google/android/search/service/IHotwordServiceCallback.aidl b/packages/Keyguard/src/com/google/android/search/service/IHotwordServiceCallback.aidl
new file mode 100644
index 0000000..7b3765f
--- /dev/null
+++ b/packages/Keyguard/src/com/google/android/search/service/IHotwordServiceCallback.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2013 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.google.android.search.service;
+
+/**
+ * Interface implemented by users of Hotword service to get callbacks
+ * for hotword events.
+ */
+oneway interface IHotwordServiceCallback {
+
+ /** Hotword detection start/stop callbacks */
+ void onHotwordDetectionStarted();
+ void onHotwordDetectionStopped();
+
+ /**
+ * Called back when hotword is detected.
+ * The action tells the client what action to take, post hotword-detection.
+ */
+ void onHotwordDetected(in String action);
+}
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index c00639d..1f10af8 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -18,7 +18,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.printspooler"
- android:sharedUserId="android.uid.printspooler"
+ android:sharedUserId="android.uid.system"
android:versionName="1"
android:versionCode="1"
coreApp="true">
@@ -51,9 +51,10 @@
</activity>
<activity
- android:name=".ChoosePrinterActivity"
- android:exported="false"
- android:theme="@android:style/Theme.Holo.Light">
+ android:name=".SelectPrinterActivity"
+ android:label="@string/all_printers_label"
+ android:theme="@style/SelectPrinterActivityTheme"
+ android:exported="false">
</activity>
<receiver
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_menu_add.png b/packages/PrintSpooler/res/drawable-hdpi/ic_menu_add.png
new file mode 100644
index 0000000..4b68f52
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-hdpi/ic_menu_add.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_menu_add.png b/packages/PrintSpooler/res/drawable-mdpi/ic_menu_add.png
new file mode 100644
index 0000000..15ffadd
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-mdpi/ic_menu_add.png
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_menu_add.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_menu_add.png
new file mode 100644
index 0000000..420510e
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable-xhdpi/ic_menu_add.png
Binary files differ
diff --git a/packages/PrintSpooler/res/layout/choose_printer_activity.xml b/packages/PrintSpooler/res/layout/choose_printer_activity.xml
deleted file mode 100644
index c34a108..0000000
--- a/packages/PrintSpooler/res/layout/choose_printer_activity.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-
-<ListView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/list_view"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:orientation="vertical">
-</ListView>
-
diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml
index a0c111b..7817094 100644
--- a/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml
+++ b/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml
@@ -20,9 +20,4 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@color/container_background">
-
- <include
- layout="@layout/print_job_config_activity_content_editing">
- </include>
-
</FrameLayout>
diff --git a/packages/PrintSpooler/res/layout/select_printer_activity.xml b/packages/PrintSpooler/res/layout/select_printer_activity.xml
new file mode 100644
index 0000000..f4e1853
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/select_printer_activity.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+
+ <fragment
+ android:name="com.android.printspooler.SelectPrinterFragment"
+ android:id="@+id/select_printer_fragment"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ </fragment>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
index 002cc14..d14c064 100644
--- a/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
+++ b/packages/PrintSpooler/res/layout/spinner_dropdown_item.xml
@@ -15,7 +15,7 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
+ android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingStart="8dip"
android:paddingEnd="8dip"
diff --git a/packages/PrintSpooler/res/menu/choose_printer_activity.xml b/packages/PrintSpooler/res/menu/select_printer_activity.xml
similarity index 79%
rename from packages/PrintSpooler/res/menu/choose_printer_activity.xml
rename to packages/PrintSpooler/res/menu/select_printer_activity.xml
index 3774279..28fbd35 100644
--- a/packages/PrintSpooler/res/menu/choose_printer_activity.xml
+++ b/packages/PrintSpooler/res/menu/select_printer_activity.xml
@@ -23,7 +23,15 @@
android:actionViewClass="android.widget.SearchView"
android:showAsAction="ifRoom"
android:alphabeticShortcut="f"
- android:imeOptions="actionSearch">
+ android:imeOptions="actionSearch">
+ </item>
+
+ <item
+ android:id="@+id/action_add_printer"
+ android:title="@null"
+ android:icon="@drawable/ic_menu_add"
+ android:showAsAction="ifRoom"
+ android:alphabeticShortcut="a">
</item>
</menu>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 1cd611f..41fc516 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -58,11 +58,32 @@
<!-- Title for the temporary dialog show while an app is generating a print job. [CHAR LIMIT=30] -->
<string name="generating_print_job">Generating print job</string>
- <!-- Choose printer activity -->
+ <!-- Title for the save as PDF option in the printer list. [CHAR LIMIT=30] -->
+ <string name="save_as_pdf">Save as PDF</string>
+
+ <!-- Title for the open all printers UI option in the printer list. [CHAR LIMIT=30] -->
+ <string name="all_printers">All printers\.\.\.</string>
+
+ <!-- Title for the searching for printers option in the printer list
+ (only option if not printers are available). [CHAR LIMIT=40] -->
+ <string name="searching_for_printers">Searching for printers\.\.\.</string>
+
+ <!-- Select printer activity -->
<!-- Title for the share action bar menu item. [CHAR LIMIT=20] -->
<string name="search">Search</string>
+ <!-- Title for the select printer activity. [CHAR LIMIT=30] -->
+ <string name="all_printers_label">All printers</string>
+
+ <!-- Add printer dialog -->
+
+ <!-- Title for the alert dialog for selecting a print service. [CHAR LIMIT=50] -->
+ <string name="choose_print_service">Choose print service</string>
+
+ <!-- Title for the button to search the play store for print services. [CHAR LIMIT=50] -->
+ <string name="search_play_store">Search in play store</string>
+
<!-- Notifications -->
<!-- Template for the notificaiton label for a printing print job. [CHAR LIMIT=25] -->
diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml
index ab16c65..831b0ec 100644
--- a/packages/PrintSpooler/res/values/themes.xml
+++ b/packages/PrintSpooler/res/values/themes.xml
@@ -24,4 +24,12 @@
<item name="android:colorBackgroundCacheHint">@android:color/transparent</item>
</style>
+ <style name="SelectPrinterActivityTheme" parent="@android:style/Theme.Holo.Light">
+ <item name="android:actionBarStyle">@style/SelectPrinterActivityActionBarStyle</item>
+ </style>
+
+ <style name="SelectPrinterActivityActionBarStyle" parent="@android:style/Widget.Holo.ActionBar">
+ <item name="android:displayOptions">showTitle</item>
+ </style>
+
</resources>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/AvailablePrinterProvider.java b/packages/PrintSpooler/src/com/android/printspooler/AvailablePrinterProvider.java
deleted file mode 100644
index 658a224..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/AvailablePrinterProvider.java
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * Copyright (C) 2013 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.printspooler;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.print.IPrinterDiscoverySessionController;
-import android.print.IPrinterDiscoverySessionObserver;
-import android.print.PrinterId;
-import android.print.PrinterInfo;
-import android.util.ArraySet;
-import android.util.Log;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-/**
- * This class is responsible to provide the available printers.
- * It starts and stops printer discovery and manages the returned
- * printers.
- */
-public class AvailablePrinterProvider extends DataProvider<PrinterInfo>
- implements DataLoader {
- private static final String LOG_TAG = "AvailablePrinterProvider";
-
- private final Set<PrinterId> mPrinteIdsSet = new ArraySet<PrinterId>();
-
- private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
-
- private final List<PrinterId> mPriorityList;
-
- private PrinterDiscoverySession mDiscoverySession;
-
- public AvailablePrinterProvider(Context context, List<PrinterId> priorityList) {
- mDiscoverySession = new PrinterDiscoverySession(context.getMainLooper());
- mPriorityList = priorityList;
- }
-
- @Override
- public void startLoadData() {
- mDiscoverySession.open();
- }
-
- @Override
- public void stopLoadData() {
- mDiscoverySession.close();
- }
-
- @Override
- public int getItemCount() {
- return mPrinters.size();
- }
-
- @Override
- public int getItemIndex(PrinterInfo printer) {
- return mPrinters.indexOf(printer);
- }
-
- @Override
- public PrinterInfo getItemAt(int index) {
- return mPrinters.get(index);
- }
-
- public void refreshItem(int index) {
- PrinterInfo printer = getItemAt(index);
- mDiscoverySession.requestPrinterUpdate(printer.getId());
- }
-
- private void addPrinters(List<PrinterInfo> printers) {
- boolean addedPrinters = false;
-
- final int addedPrinterCount = printers.size();
- for (int i = 0; i < addedPrinterCount; i++) {
- PrinterInfo addedPrinter = printers.get(i);
- if (mPrinteIdsSet.add(addedPrinter.getId())) {
- mPrinters.add(addedPrinter);
- addedPrinters = true;
- }
- }
-
- if (addedPrinters) {
- notifyChanged();
- }
- }
-
- private void updatePrinters(List<PrinterInfo> printers) {
- boolean updatedPrinters = false;
-
- final int updatedPrinterCount = printers.size();
- for (int i = 0; i < updatedPrinterCount; i++) {
- PrinterInfo updatedPrinter = printers.get(i);
- if (mPrinteIdsSet.contains(updatedPrinter.getId())) {
- final int oldPrinterCount = mPrinters.size();
- for (int j = 0; j < oldPrinterCount; j++) {
- PrinterInfo oldPrinter = mPrinters.get(j);
- if (updatedPrinter.getId().equals(oldPrinter.getId())) {
- mPrinters.set(j, updatedPrinter);
- updatedPrinters = true;
- break;
- }
- }
- }
- }
-
- if (updatedPrinters) {
- notifyChanged();
- }
- }
-
- private void removePrinters(List<PrinterId> printers) {
- boolean removedPrinters = false;
-
- final int removedPrinterCount = printers.size();
- for (int i = 0; i < removedPrinterCount; i++) {
- PrinterId removedPrinter = printers.get(i);
- if (mPrinteIdsSet.contains(removedPrinter)) {
- mPrinteIdsSet.remove(removedPrinter);
- Iterator<PrinterInfo> iterator = mPrinters.iterator();
- while (iterator.hasNext()) {
- PrinterInfo oldPrinter = iterator.next();
- if (removedPrinter.equals(oldPrinter.getId())) {
- iterator.remove();
- break;
- }
- }
- }
- }
-
- if (removedPrinters) {
- notifyChanged();
- }
- }
-
- private final class PrinterDiscoverySession {
-
- private final Handler mHandler;
-
- private final IPrinterDiscoverySessionObserver mObserver;
-
- private IPrinterDiscoverySessionController mController;
-
- public PrinterDiscoverySession(Looper looper) {
- mHandler = new SessionHandler(looper);
- mObserver = new PrinterDiscoverySessionObserver(this);
- }
-
- public void open() {
- PrintSpooler.peekInstance().createPrinterDiscoverySession(
- mObserver);
- }
-
- public void close() {
- if (mController != null) {
- try {
- mController.close();
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error closing printer discovery session", re);
- } finally {
- mController = null;
- }
- }
- }
-
- public void requestPrinterUpdate(PrinterId printerId) {
- if (mController != null) {
- try {
- mController.requestPrinterUpdate(printerId);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error requesting printer udpdate", re);
- }
- }
- }
-
- private final class SessionHandler extends Handler {
- public static final int MSG_SET_CONTROLLER = 1;
- public static final int MSG_ON_PRINTERS_ADDED = 2;
- public static final int MSG_ON_PRINTERS_REMOVED = 3;
- public static final int MSG_ON_PRINTERS_UPDATED = 4;
-
- public SessionHandler(Looper looper) {
- super(looper, null, false);
- }
-
- @Override
- @SuppressWarnings("unchecked")
- public void handleMessage(Message message) {
- switch (message.what) {
- case MSG_SET_CONTROLLER: {
- mController = (IPrinterDiscoverySessionController) message.obj;
- try {
- mController.open(mPriorityList);
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "Error starting printer discovery");
- }
- } break;
-
- case MSG_ON_PRINTERS_ADDED: {
- List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
- addPrinters(printers);
- } break;
-
- case MSG_ON_PRINTERS_REMOVED: {
- List<PrinterId> printers = (List<PrinterId>) message.obj;
- removePrinters(printers);
- } break;
-
- case MSG_ON_PRINTERS_UPDATED: {
- List<PrinterInfo> printers = (List<PrinterInfo>) message.obj;
- updatePrinters(printers);
- } break;
- };
- }
- }
- }
-
- private static final class PrinterDiscoverySessionObserver
- extends IPrinterDiscoverySessionObserver.Stub {
-
- private final WeakReference<PrinterDiscoverySession> mWeakSession;
-
- public PrinterDiscoverySessionObserver(PrinterDiscoverySession session) {
- mWeakSession = new WeakReference<PrinterDiscoverySession>(session);
- }
-
- @Override
- public void setController(IPrinterDiscoverySessionController controller) {
- PrinterDiscoverySession sesison = mWeakSession.get();
- if (sesison != null) {
- sesison.mHandler.obtainMessage(
- PrinterDiscoverySession.SessionHandler.MSG_SET_CONTROLLER,
- controller).sendToTarget();
- }
- }
-
- @Override
- public void onPrintersAdded(List<PrinterInfo> printers) {
- PrinterDiscoverySession sesison = mWeakSession.get();
- if (sesison != null) {
- sesison.mHandler.obtainMessage(
- PrinterDiscoverySession.SessionHandler.MSG_ON_PRINTERS_ADDED,
- printers).sendToTarget();
- }
- }
-
- @Override
- public void onPrintersRemoved(List<PrinterId> printers) {
- PrinterDiscoverySession session = mWeakSession.get();
- if (session != null) {
- session.mHandler.obtainMessage(
- PrinterDiscoverySession.SessionHandler.MSG_ON_PRINTERS_REMOVED,
- printers).sendToTarget();
- }
- }
-
- @Override
- public void onPrintersUpdated(List<PrinterInfo> printers) {
- PrinterDiscoverySession session = mWeakSession.get();
- if (session != null) {
- session.mHandler.obtainMessage(
- PrinterDiscoverySession.SessionHandler.MSG_ON_PRINTERS_UPDATED,
- printers).sendToTarget();
- }
- }
- };
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ChoosePrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ChoosePrinterActivity.java
deleted file mode 100644
index 8b0dd66a..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/ChoosePrinterActivity.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2013 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.printspooler;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class ChoosePrinterActivity extends Activity {
-
- @Override
- public void onCreate(Bundle bundle) {
- setContentView(R.layout.choose_printer_activity);
- }
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/DataLoader.java b/packages/PrintSpooler/src/com/android/printspooler/DataLoader.java
deleted file mode 100644
index 82cc2e1..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/DataLoader.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2013 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.printspooler;
-
-/**
- * This is the contract for a class that know how to load data.
- */
-public interface DataLoader {
-
- /**
- * Requests to start loading data.
- */
- public void startLoadData();
-
- /**
- * Requests to stop loading data.
- */
- public void stopLoadData();
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/DataProvider.java b/packages/PrintSpooler/src/com/android/printspooler/DataProvider.java
deleted file mode 100644
index 7b10903..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/DataProvider.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2013 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.printspooler;
-
-import android.database.DataSetObservable;
-
-/**
- * This is the simple contract for data providers.
- *
- * @param <T> The type of the providers data.
- */
-public abstract class DataProvider<T> extends DataSetObservable {
-
- /**
- * Gets the number of items.
- *
- * @return The item count.
- */
- public abstract int getItemCount();
-
- /**
- * Gets the index of an item.
- *
- * @param item The item.
- * @return The item index.
- */
- public abstract int getItemIndex(T item);
-
- /**
- * Gets an item at a given position.
- *
- * @param index The position.
- * @return The item.
- */
- public abstract T getItemAt(int index);
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/FavoritePrinterProvider.java b/packages/PrintSpooler/src/com/android/printspooler/FavoritePrinterProvider.java
deleted file mode 100644
index 2c539d1..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/FavoritePrinterProvider.java
+++ /dev/null
@@ -1,364 +0,0 @@
-/*
- * Copyright (C) 2013 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.printspooler;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.AsyncTask;
-import android.os.Build;
-import android.print.PrinterId;
-import android.print.PrinterInfo;
-import android.util.ArrayMap;
-import android.util.AtomicFile;
-import android.util.Log;
-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;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-/**
- * This class provides the favorite printers based on past usage.
- */
-final class FavoritePrinterProvider extends DataProvider<PrinterInfo> implements DataLoader {
-
- private static final String LOG_TAG = "FavoritePrinterProvider";
-
- private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
-
- private static final int MAX_HISTORY_LENGTH = 50;
-
- private static final double WEIGHT_DECAY_COEFFICIENT = 0.95f;
-
- private final List<PrinterRecord> mHistoricalPrinters = new ArrayList<PrinterRecord>();
-
- private final List<PrinterRecord> mFavoritePrinters = new ArrayList<PrinterRecord>();
-
- private final PersistenceManager mPersistenceManager;
-
- public FavoritePrinterProvider(Context context) {
- mPersistenceManager = new PersistenceManager(context);
- }
-
- public void addPrinter(PrinterInfo printer) {
- addPrinterInternal(printer);
- computeFavoritePrinters();
- mPersistenceManager.writeState();
- }
-
- @Override
- public int getItemCount() {
- return mFavoritePrinters.size();
- }
-
- @Override
- public PrinterInfo getItemAt(int index) {
- return mFavoritePrinters.get(index).printer;
- }
-
- @Override
- public int getItemIndex(PrinterInfo printer) {
- return mFavoritePrinters.indexOf(printer);
- }
-
- @Override
- public void startLoadData() {
- mPersistenceManager.readStateLocked();
- computeFavoritePrinters();
- }
-
- @Override
- public void stopLoadData() {
- /* do nothing */
- }
-
- private void addPrinterInternal(PrinterInfo printer) {
- if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) {
- mHistoricalPrinters.remove(0);
- }
- mHistoricalPrinters.add(new PrinterRecord(printer));
- }
-
- private void computeFavoritePrinters() {
- Map<PrinterId, PrinterRecord> recordMap =
- new ArrayMap<PrinterId, PrinterRecord>();
-
- // Recompute the weights.
- float currentWeight = 1.0f;
- final int printerCount = mHistoricalPrinters.size();
- for (int i = printerCount - 1; i >= 0; i--) {
- PrinterRecord record = mHistoricalPrinters.get(i);
- record.weight = currentWeight;
- // Aggregate weight for the same printer
- PrinterRecord oldRecord = recordMap.put(record.printer.getId(), record);
- if (oldRecord != null) {
- record.weight += oldRecord.weight;
- }
- currentWeight *= WEIGHT_DECAY_COEFFICIENT;
- }
-
- // Copy the unique printer records with computed weights.
- mFavoritePrinters.addAll(recordMap.values());
-
- // Soft the favorite printers.
- Collections.sort(mFavoritePrinters);
- }
-
- private final class PrinterRecord implements Comparable<PrinterRecord> {
- public final PrinterInfo printer;
- public float weight;
-
- public PrinterRecord(PrinterInfo printer) {
- this.printer = printer;
- }
-
- @Override
- public int compareTo(PrinterRecord another) {
- return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight);
- }
- }
-
- private final class PersistenceManager {
- private static final String PERSIST_FILE_NAME = "printer_history.xml";
-
- private static final String TAG_PRINTERS = "printers";
-
- private static final String TAG_PRINTER = "printer";
- 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_NAME = "name";
- private static final String ATTR_DESCRIPTION = "description";
- private static final String ATTR_STATUS = "status";
-
- private final AtomicFile mStatePersistFile;
-
- private PersistenceManager(Context context) {
- mStatePersistFile = new AtomicFile(new File(context.getFilesDir(),
- PERSIST_FILE_NAME));
- }
-
- @SuppressWarnings("unchecked")
- public void writeState() {
-
- new AsyncTask<List<PrinterRecord>, Void, Void>() {
- @Override
- protected Void doInBackground(List<PrinterRecord>... printers) {
- doWriteState(printers[0]);
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- notifyChanged();
- }
-
- }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR,
- new ArrayList<PrinterRecord>(mHistoricalPrinters));
- }
-
- private void doWriteState(List<PrinterRecord> printers) {
- FileOutputStream out = null;
- try {
- out = mStatePersistFile.startWrite();
-
- XmlSerializer serializer = new FastXmlSerializer();
- serializer.setOutput(out, "utf-8");
- serializer.startDocument(null, true);
- serializer.startTag(null, TAG_PRINTERS);
-
- final int printerCount = printers.size();
- for (int i = printerCount - 1; i >= 0; i--) {
- PrinterInfo printer = printers.get(i).printer;
-
- serializer.startTag(null, TAG_PRINTER);
-
- serializer.attribute(null, ATTR_NAME, printer.getName());
- serializer.attribute(null, ATTR_STATUS, String.valueOf(printer.getStatus()));
- String description = printer.getDescription();
- if (description != null) {
- serializer.attribute(null, ATTR_DESCRIPTION, description);
- }
-
- PrinterId printerId = printer.getId();
- serializer.startTag(null, TAG_PRINTER_ID);
- serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
- serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
- .flattenToString());
- serializer.endTag(null, TAG_PRINTER_ID);
-
- serializer.endTag(null, TAG_PRINTER);
-
- if (DEBUG) {
- Log.i(LOG_TAG, "[PERSISTED] " + printer);
- }
- }
-
- serializer.endTag(null, TAG_PRINTERS);
- serializer.endDocument();
- mStatePersistFile.finishWrite(out);
-
- if (DEBUG) {
- Log.i(LOG_TAG, "[PERSIST END]");
- }
- } catch (IOException ioe) {
- Slog.w(LOG_TAG, "Failed to write printer history, restoring backup.", ioe);
- mStatePersistFile.failWrite(out);
- } finally {
- IoUtils.closeQuietly(out);
- }
- }
-
- public void readStateLocked() {
- FileInputStream in = null;
- try {
- in = mStatePersistFile.openRead();
- } catch (FileNotFoundException e) {
- Log.i(LOG_TAG, "No existing printer history.");
- return;
- }
- try {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(in, null);
- parseState(parser);
- } catch (IllegalStateException ise) {
- Slog.w(LOG_TAG, "Failed parsing ", ise);
- } catch (NullPointerException npe) {
- Slog.w(LOG_TAG, "Failed parsing ", npe);
- } catch (NumberFormatException nfe) {
- Slog.w(LOG_TAG, "Failed parsing ", nfe);
- } catch (XmlPullParserException xppe) {
- Slog.w(LOG_TAG, "Failed parsing ", xppe);
- } catch (IOException ioe) {
- Slog.w(LOG_TAG, "Failed parsing ", ioe);
- } catch (IndexOutOfBoundsException iobe) {
- Slog.w(LOG_TAG, "Failed parsing ", iobe);
- } finally {
- IoUtils.closeQuietly(in);
- }
- notifyChanged();
- }
-
- private void parseState(XmlPullParser parser)
- throws IOException, XmlPullParserException {
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.START_TAG, TAG_PRINTERS);
- parser.next();
-
- while (parsePrinter(parser)) {
- parser.next();
- }
-
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_PRINTERS);
-
- // We were reading the new records first and appended them first,
- // hence the historical list is in a reversed order, so fix that.
- Collections.reverse(mHistoricalPrinters);
- }
-
- private boolean parsePrinter(XmlPullParser parser)
- throws IOException, XmlPullParserException {
- skipEmptyTextTags(parser);
- if (!accept(parser, XmlPullParser.START_TAG, TAG_PRINTER)) {
- return false;
- }
-
- String name = parser.getAttributeValue(null, ATTR_NAME);
- String description = parser.getAttributeValue(null, ATTR_DESCRIPTION);
- final int status = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATUS));
-
- parser.next();
-
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID);
- String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
- ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
- null, ATTR_SERVICE_NAME));
- PrinterId printerId = new PrinterId(service, localId);
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
- parser.next();
-
- PrinterInfo.Builder builder = new PrinterInfo.Builder(printerId, name, status);
- builder.setDescription(description);
- PrinterInfo printer = builder.create();
-
- addPrinterInternal(printer);
-
- if (DEBUG) {
- Log.i(LOG_TAG, "[RESTORED] " + printer);
- }
-
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_PRINTER);
-
- return true;
- }
-
- private void expect(XmlPullParser parser, int type, String tag)
- throws IOException, XmlPullParserException {
- if (!accept(parser, type, tag)) {
- throw new XmlPullParserException("Exepected event: " + type
- + " and tag: " + tag + " but got event: " + parser.getEventType()
- + " and tag:" + parser.getName());
- }
- }
-
- private void skipEmptyTextTags(XmlPullParser parser)
- throws IOException, XmlPullParserException {
- while (accept(parser, XmlPullParser.TEXT, null)
- && "\n".equals(parser.getText())) {
- parser.next();
- }
- }
-
- private boolean accept(XmlPullParser parser, int type, String tag)
- throws IOException, XmlPullParserException {
- if (parser.getEventType() != type) {
- return false;
- }
- if (tag != null) {
- if (!tag.equals(parser.getName())) {
- return false;
- }
- } else if (parser.getName() != null) {
- return false;
- }
- return true;
- }
- }
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
new file mode 100644
index 0000000..6bad5b3
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java
@@ -0,0 +1,575 @@
+/*
+ * Copyright (C) 2013 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.printspooler;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Loader;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.printspooler.PrintSpoolerService.PrinterDiscoverySession;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class is responsible for loading printers by doing discovery
+ * and merging the discovered printers with the previously used ones.
+ */
+public class FusedPrintersProvider extends Loader<List<PrinterInfo>> {
+ private static final String LOG_TAG = "FusedPrintersProvider";
+
+ private static final boolean DEBUG = true && Build.IS_DEBUGGABLE;
+
+ private static final double WEIGHT_DECAY_COEFFICIENT = 0.95f;
+
+ private static final int MAX_HISTORY_LENGTH = 50;
+
+ private static final int MAX_HISTORICAL_PRINTER_COUNT = 4;
+
+ private final Map<PrinterId, PrinterInfo> mPrinters =
+ new LinkedHashMap<PrinterId, PrinterInfo>();
+
+ private final PersistenceManager mPersistenceManager;
+
+ private PrinterDiscoverySession mDiscoverySession;
+
+ private List<PrinterInfo> mFavoritePrinters;
+
+ public FusedPrintersProvider(Context context) {
+ super(context);
+ mPersistenceManager = new PersistenceManager(context);
+ }
+
+ public void addHistoricalPrinter(PrinterInfo printer) {
+ mPersistenceManager.addPrinterAndWritePrinterHistory(printer);
+ }
+
+ public List<PrinterInfo> getPrinters() {
+ return new ArrayList<PrinterInfo>(mPrinters.values());
+ }
+
+ @Override
+ public void deliverResult(List<PrinterInfo> printers) {
+ if (isStarted()) {
+ super.deliverResult(printers);
+ }
+ }
+
+ @Override
+ protected void onStartLoading() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "onStartLoading()");
+ }
+ // The contract is that if we already have a valid,
+ // result the we have to deliver it immediately.
+ if (!mPrinters.isEmpty()) {
+ deliverResult(new ArrayList<PrinterInfo>(mPrinters.values()));
+ }
+ // If the data has changed since the last load
+ // or is not available, start a load.
+ if (takeContentChanged() || mPrinters.isEmpty()) {
+ onForceLoad();
+ }
+ }
+
+ @Override
+ protected void onStopLoading() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "onStopLoading()");
+ }
+ onCancelLoad();
+ }
+
+ @Override
+ protected void onForceLoad() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "onForceLoad()");
+ }
+ onCancelLoad();
+ loadInternal();
+ }
+
+ private void loadInternal() {
+ if (mDiscoverySession == null) {
+ mDiscoverySession = new MyPrinterDiscoverySession();
+ mPersistenceManager.readPrinterHistory();
+ }
+ if (mPersistenceManager.isReadHistoryCompleted()
+ && !mDiscoverySession.isStarted()) {
+ final int favoriteCount = Math.min(MAX_HISTORICAL_PRINTER_COUNT,
+ mFavoritePrinters.size());
+ List<PrinterId> printerIds = new ArrayList<PrinterId>(favoriteCount);
+ for (int i = 0; i < favoriteCount; i++) {
+ printerIds.add(mFavoritePrinters.get(i).getId());
+ }
+ mDiscoverySession.startPrinterDisovery(printerIds);
+ }
+ }
+
+ @Override
+ protected boolean onCancelLoad() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "onCancelLoad()");
+ }
+ return cancelInternal();
+ }
+
+ private boolean cancelInternal() {
+ if (mDiscoverySession != null && mDiscoverySession.isStarted()) {
+ mDiscoverySession.stopPrinterDiscovery();
+ return true;
+ } else if (mPersistenceManager.isReadHistoryInProgress()) {
+ return mPersistenceManager.stopReadPrinterHistory();
+ }
+ return false;
+ }
+
+ @Override
+ protected void onReset() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "onReset()");
+ }
+ onStopLoading();
+ mPrinters.clear();
+ if (mDiscoverySession != null) {
+ mDiscoverySession.destroy();
+ mDiscoverySession = null;
+ }
+ }
+
+ @Override
+ protected void onAbandon() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "onAbandon()");
+ }
+ onStopLoading();
+ }
+
+ public void refreshPrinter(PrinterId printerId) {
+ if (isStarted() && mDiscoverySession != null && mDiscoverySession.isStarted()) {
+ mDiscoverySession.requestPrinterUpdated(printerId);
+ }
+ }
+
+ private final class MyPrinterDiscoverySession extends PrinterDiscoverySession {
+
+ @Override
+ public void onPrintersAdded(List<PrinterInfo> printers) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "MyPrinterDiscoverySession#onPrintersAdded()");
+ }
+ boolean printersAdded = false;
+ final int addedPrinterCount = printers.size();
+ for (int i = 0; i < addedPrinterCount; i++) {
+ PrinterInfo printer = printers.get(i);
+ if (!mPrinters.containsKey(printer.getId())) {
+ mPrinters.put(printer.getId(), printer);
+ printersAdded = true;
+ }
+ }
+ if (printersAdded) {
+ deliverResult(new ArrayList<PrinterInfo>(mPrinters.values()));
+ }
+ }
+
+ @Override
+ public void onPrintersRemoved(List<PrinterId> printerIds) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "MyPrinterDiscoverySession#onPrintersRemoved()");
+ }
+ boolean removedPrinters = false;
+ final int removedPrinterCount = printerIds.size();
+ for (int i = 0; i < removedPrinterCount; i++) {
+ PrinterId removedPrinterId = printerIds.get(i);
+ if (mPrinters.remove(removedPrinterId) != null) {
+ removedPrinters = true;
+ }
+ }
+ if (removedPrinters) {
+ deliverResult(new ArrayList<PrinterInfo>(mPrinters.values()));
+ }
+ }
+
+ @Override
+ public void onPrintersUpdated(List<PrinterInfo> printers) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "MyPrinterDiscoverySession#onPrintersUpdated()");
+ }
+ boolean updatedPrinters = false;
+ final int updatedPrinterCount = printers.size();
+ for (int i = 0; i < updatedPrinterCount; i++) {
+ PrinterInfo updatedPrinter = printers.get(i);
+ if (mPrinters.containsKey(updatedPrinter.getId())) {
+ mPrinters.put(updatedPrinter.getId(), updatedPrinter);
+ updatedPrinters = true;
+ }
+ }
+ if (updatedPrinters) {
+ deliverResult(new ArrayList<PrinterInfo>(mPrinters.values()));
+ }
+ }
+ }
+
+ private final class PersistenceManager {
+ private static final String PERSIST_FILE_NAME = "printer_history.xml";
+
+ private static final String TAG_PRINTERS = "printers";
+
+ private static final String TAG_PRINTER = "printer";
+ 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_NAME = "name";
+ private static final String ATTR_DESCRIPTION = "description";
+ private static final String ATTR_STATUS = "status";
+
+ private final AtomicFile mStatePersistFile;
+
+ private List<PrinterInfo> mHistoricalPrinters;
+
+ private boolean mReadHistoryCompleted;
+ private boolean mReadHistoryInProgress;
+
+ private final AsyncTask<Void, Void, List<PrinterInfo>> mReadTask =
+ new AsyncTask<Void, Void, List<PrinterInfo>>() {
+ @Override
+ protected List<PrinterInfo> doInBackground(Void... args) {
+ return doReadPrinterHistory();
+ }
+
+ @Override
+ protected void onPostExecute(List<PrinterInfo> printers) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "read history completed");
+ }
+
+ mHistoricalPrinters = printers;
+
+ // Compute the favorite printers.
+ mFavoritePrinters = computeFavoritePrinters(printers);
+
+ // We want the first few favorite printers on top of the list.
+ final int favoriteCount = Math.min(mFavoritePrinters.size(),
+ MAX_HISTORICAL_PRINTER_COUNT);
+ for (int i = 0; i < favoriteCount; i++) {
+ PrinterInfo favoritePrinter = mFavoritePrinters.get(i);
+ mPrinters.put(favoritePrinter.getId(), favoritePrinter);
+ }
+
+ mReadHistoryInProgress = false;
+ mReadHistoryCompleted = true;
+
+ loadInternal();
+ }
+
+ private List<PrinterInfo> doReadPrinterHistory() {
+ FileInputStream in = null;
+ try {
+ in = mStatePersistFile.openRead();
+ } catch (FileNotFoundException fnfe) {
+ Log.i(LOG_TAG, "No existing printer history.");
+ return new ArrayList<PrinterInfo>();
+ }
+ try {
+ List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+ parseState(parser, printers);
+ return printers;
+ } catch (IllegalStateException ise) {
+ Slog.w(LOG_TAG, "Failed parsing ", ise);
+ } catch (NullPointerException npe) {
+ Slog.w(LOG_TAG, "Failed parsing ", npe);
+ } catch (NumberFormatException nfe) {
+ Slog.w(LOG_TAG, "Failed parsing ", nfe);
+ } catch (XmlPullParserException xppe) {
+ Slog.w(LOG_TAG, "Failed parsing ", xppe);
+ } catch (IOException ioe) {
+ Slog.w(LOG_TAG, "Failed parsing ", ioe);
+ } catch (IndexOutOfBoundsException iobe) {
+ Slog.w(LOG_TAG, "Failed parsing ", iobe);
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+
+ return Collections.emptyList();
+ }
+
+ private void parseState(XmlPullParser parser, List<PrinterInfo> outPrinters)
+ throws IOException, XmlPullParserException {
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_PRINTERS);
+ parser.next();
+
+ while (parsePrinter(parser, outPrinters)) {
+ // Be nice and respond to cancellation
+ if (isCancelled()) {
+ return;
+ }
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_PRINTERS);
+ }
+
+ private boolean parsePrinter(XmlPullParser parser, List<PrinterInfo> outPrinters)
+ throws IOException, XmlPullParserException {
+ skipEmptyTextTags(parser);
+ if (!accept(parser, XmlPullParser.START_TAG, TAG_PRINTER)) {
+ return false;
+ }
+
+ String name = parser.getAttributeValue(null, ATTR_NAME);
+ String description = parser.getAttributeValue(null, ATTR_DESCRIPTION);
+ final int status = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATUS));
+
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID);
+ String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
+ ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
+ null, ATTR_SERVICE_NAME));
+ PrinterId printerId = new PrinterId(service, localId);
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
+ parser.next();
+
+ PrinterInfo.Builder builder = new PrinterInfo.Builder(printerId, name, status);
+ builder.setDescription(description);
+ PrinterInfo printer = builder.create();
+
+ outPrinters.add(printer);
+
+ if (DEBUG) {
+ Log.i(LOG_TAG, "[RESTORED] " + printer);
+ }
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_PRINTER);
+
+ return true;
+ }
+
+ private void expect(XmlPullParser parser, int type, String tag)
+ throws IOException, XmlPullParserException {
+ if (!accept(parser, type, tag)) {
+ throw new XmlPullParserException("Exepected event: " + type
+ + " and tag: " + tag + " but got event: " + parser.getEventType()
+ + " and tag:" + parser.getName());
+ }
+ }
+
+ private void skipEmptyTextTags(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ while (accept(parser, XmlPullParser.TEXT, null)
+ && "\n".equals(parser.getText())) {
+ parser.next();
+ }
+ }
+
+ private boolean accept(XmlPullParser parser, int type, String tag)
+ throws IOException, XmlPullParserException {
+ if (parser.getEventType() != type) {
+ return false;
+ }
+ if (tag != null) {
+ if (!tag.equals(parser.getName())) {
+ return false;
+ }
+ } else if (parser.getName() != null) {
+ return false;
+ }
+ return true;
+ }
+ };
+
+ private final AsyncTask<List<PrinterInfo>, Void, Void> mWriteTask =
+ new AsyncTask<List<PrinterInfo>, Void, Void>() {
+ @Override
+ protected Void doInBackground(List<PrinterInfo>... printers) {
+ doWritePrinterHistory(printers[0]);
+ return null;
+ }
+
+ private void doWritePrinterHistory(List<PrinterInfo> printers) {
+ FileOutputStream out = null;
+ try {
+ out = mStatePersistFile.startWrite();
+
+ XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(out, "utf-8");
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_PRINTERS);
+
+ final int printerCount = printers.size();
+ for (int i = 0; i < printerCount; i++) {
+ PrinterInfo printer = printers.get(i);
+
+ serializer.startTag(null, TAG_PRINTER);
+
+ serializer.attribute(null, ATTR_NAME, printer.getName());
+ serializer.attribute(null, ATTR_STATUS, String.valueOf(
+ printer.getStatus()));
+ String description = printer.getDescription();
+ if (description != null) {
+ serializer.attribute(null, ATTR_DESCRIPTION, description);
+ }
+
+ PrinterId printerId = printer.getId();
+ serializer.startTag(null, TAG_PRINTER_ID);
+ serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
+ serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
+ .flattenToString());
+ serializer.endTag(null, TAG_PRINTER_ID);
+
+ serializer.endTag(null, TAG_PRINTER);
+
+ if (DEBUG) {
+ Log.i(LOG_TAG, "[PERSISTED] " + printer);
+ }
+ }
+
+ serializer.endTag(null, TAG_PRINTERS);
+ serializer.endDocument();
+ mStatePersistFile.finishWrite(out);
+
+ if (DEBUG) {
+ Log.i(LOG_TAG, "[PERSIST END]");
+ }
+ } catch (IOException ioe) {
+ Slog.w(LOG_TAG, "Failed to write printer history, restoring backup.", ioe);
+ mStatePersistFile.failWrite(out);
+ } finally {
+ IoUtils.closeQuietly(out);
+ }
+ }
+ };
+
+ private PersistenceManager(Context context) {
+ mStatePersistFile = new AtomicFile(new File(context.getFilesDir(),
+ PERSIST_FILE_NAME));
+ }
+
+ public boolean isReadHistoryInProgress() {
+ return mReadHistoryInProgress;
+ }
+
+ public boolean isReadHistoryCompleted() {
+ return mReadHistoryCompleted;
+ }
+
+ public boolean stopReadPrinterHistory() {
+ return mReadTask.cancel(true);
+ }
+
+ public void readPrinterHistory() {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "read history started");
+ }
+ mReadHistoryInProgress = true;
+ mReadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void addPrinterAndWritePrinterHistory(PrinterInfo printer) {
+ if (mHistoricalPrinters.size() >= MAX_HISTORY_LENGTH) {
+ mHistoricalPrinters.remove(0);
+ }
+ mHistoricalPrinters.add(printer);
+ mWriteTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, mHistoricalPrinters);
+ }
+
+ private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) {
+ Map<PrinterId, PrinterRecord> recordMap =
+ new ArrayMap<PrinterId, PrinterRecord>();
+
+ // Recompute 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());
+ if (record == null) {
+ record = new PrinterRecord(printer);
+ recordMap.put(printer.getId(), record);
+ }
+ record.weight += currentWeight;
+ currentWeight *= WEIGHT_DECAY_COEFFICIENT;
+ }
+
+ // Soft the favorite printers.
+ List<PrinterRecord> favoriteRecords = new ArrayList<PrinterRecord>(
+ recordMap.values());
+ Collections.sort(favoriteRecords);
+
+ // Write the favorites to the output.
+ final int favoriteCount = favoriteRecords.size();
+ List<PrinterInfo> favoritePrinters = new ArrayList<PrinterInfo>(favoriteCount);
+ for (int i = 0; i < favoriteCount; i++) {
+ PrinterInfo printer = favoriteRecords.get(i).printer;
+ favoritePrinters.add(printer);
+ }
+
+ return favoritePrinters;
+ }
+
+ private final class PrinterRecord implements Comparable<PrinterRecord> {
+ public final PrinterInfo printer;
+ public float weight;
+
+ public PrinterRecord(PrinterInfo printer) {
+ this.printer = printer;
+ }
+
+ @Override
+ public int compareTo(PrinterRecord another) {
+ return Float.floatToIntBits(another.weight) - Float.floatToIntBits(weight);
+ }
+ }
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
index f8e9f43..d3dd8c9 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
@@ -18,13 +18,17 @@
import android.app.Activity;
import android.app.Dialog;
+import android.app.LoaderManager;
import android.content.Context;
+import android.content.Intent;
+import android.content.Loader;
import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.DataSetObserver;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
@@ -39,9 +43,11 @@
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Resolution;
import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentInfo;
import android.print.PrintJobInfo;
+import android.print.PrintManager;
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
@@ -69,6 +75,14 @@
import android.widget.Spinner;
import android.widget.TextView;
+import libcore.io.IoUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
@@ -93,15 +107,32 @@
public static final String EXTRA_PRINT_ATTRIBUTES = "printAttributes";
public static final String EXTRA_PRINT_JOB_ID = "printJobId";
- private static final int CONTROLLER_STATE_INITIALIZED = 1;
- private static final int CONTROLLER_STATE_STARTED = 2;
- private static final int CONTROLLER_STATE_LAYOUT_STARTED = 3;
- private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 4;
- private static final int CONTROLLER_STATE_WRITE_STARTED = 5;
- private static final int CONTROLLER_STATE_WRITE_COMPLETED = 6;
- private static final int CONTROLLER_STATE_FINISHED = 7;
- private static final int CONTROLLER_STATE_FAILED = 8;
- private static final int CONTROLLER_STATE_CANCELLED = 9;
+ public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
+
+ private static final int LOADER_ID_PRINTERS_LOADER = 1;
+
+ private static final int DEST_ADAPTER_MIN_ITEM_COUNT = 2;
+ private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9;
+
+ private static final int DEST_ADAPTER_POSITION_SEARCHING_FOR_PRINTERS = 0;
+ private static final int DEST_ADAPTER_POSITION_SAVE_AS_PDF = 1;
+
+ private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE;
+ private static final int DEST_ADAPTER_ITEM_ID_ALL_PRINTERS = Integer.MAX_VALUE - 1;
+ private static final int DEST_ADAPTER_ITEM_ID_SEARCHING_FOR_PRINTERS = Integer.MAX_VALUE - 2;
+
+ private static final int ACTIVITY_REQUEST_CREATE_FILE = 1;
+ private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2;
+
+ private static final int CONTROLLER_STATE_FINISHED = 1;
+ private static final int CONTROLLER_STATE_FAILED = 2;
+ private static final int CONTROLLER_STATE_CANCELLED = 3;
+ private static final int CONTROLLER_STATE_INITIALIZED = 4;
+ private static final int CONTROLLER_STATE_STARTED = 5;
+ private static final int CONTROLLER_STATE_LAYOUT_STARTED = 6;
+ private static final int CONTROLLER_STATE_LAYOUT_COMPLETED = 7;
+ private static final int CONTROLLER_STATE_WRITE_STARTED = 8;
+ private static final int CONTROLLER_STATE_WRITE_COMPLETED = 9;
private static final int EDITOR_STATE_INITIALIZED = 1;
private static final int EDITOR_STATE_CONFIRMED_PRINT = 2;
@@ -109,6 +140,7 @@
private static final int EDITOR_STATE_CANCELLED = 4;
private static final int MIN_COPIES = 1;
+ private static final String MIN_COPIES_STRING = String.valueOf(MIN_COPIES);
private static final Pattern PATTERN_DIGITS = Pattern.compile("\\d");
@@ -135,10 +167,6 @@
private Document mDocument;
private PrintController mController;
- private AvailablePrinterProvider mAvailablePrinters;
-
- private FavoritePrinterProvider mFavoritePrinters;
-
private int mPrintJobId;
private IBinder mIPrintDocumentAdapter;
@@ -148,9 +176,6 @@
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
- setContentView(R.layout.print_job_config_activity_container);
-
- getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
Bundle extras = getIntent().getExtras();
@@ -169,15 +194,17 @@
mCurrPrintAttributes.copyFrom(attributes);
}
- // TODO: Use history
- mAvailablePrinters = new AvailablePrinterProvider(this, null);
- mFavoritePrinters = new FavoritePrinterProvider(this);
+ setContentView(R.layout.print_job_config_activity_container);
+
+ // TODO: This should be on the style
+ getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+
mEditor = new Editor();
mDocument = new Document();
mController = new PrintController(new RemotePrintDocumentAdapter(
IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter),
- PrintSpooler.peekInstance().generateFileForPrintJob(mPrintJobId)));
+ PrintSpoolerService.peekInstance().generateFileForPrintJob(mPrintJobId)));
try {
mIPrintDocumentAdapter.linkToDeath(mDeathRecipient, 0);
@@ -191,26 +218,6 @@
}
@Override
- protected void onResume() {
- super.onResume();
- // TODO: Polish this
- if (!mEditor.isPrintConfirmed()) {
- mAvailablePrinters.startLoadData();
- mFavoritePrinters.startLoadData();
- }
- }
-
- @Override
- protected void onPause() {
- // TODO: Polish this
- if (!mEditor.isPrintConfirmed()) {
- mAvailablePrinters.stopLoadData();
- mFavoritePrinters.stopLoadData();
- }
- super.onPause();
- }
-
- @Override
protected void onDestroy() {
// We can safely do the work in here since at this point
// the system is bound to our (spooler) process which
@@ -219,10 +226,10 @@
mController.finish();
}
if (mEditor.isPrintConfirmed() && mController.isFinished()) {
- PrintSpooler.peekInstance().setPrintJobState(mPrintJobId,
+ PrintSpoolerService.peekInstance().setPrintJobState(mPrintJobId,
PrintJobInfo.STATE_QUEUED, null);
} else {
- PrintSpooler.peekInstance().setPrintJobState(mPrintJobId,
+ PrintSpoolerService.peekInstance().setPrintJobState(mPrintJobId,
PrintJobInfo.STATE_CANCELED, null);
}
mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0);
@@ -333,13 +340,13 @@
public void update() {
if (!printAttributesChanged()) {
- // If the attributes changes, then we do not do a layout but may
+ // If the attributes changed, then we do not do a layout but may
// have to ask the app to write some pages. Hence, pretend layout
// completed and nothing changed, so we handle writing as usual.
handleOnLayoutFinished(mDocument.info, false, mRequestCounter.get());
} else {
- PrintSpooler.peekInstance().setPrintJobAttributesNoPersistence(mPrintJobId,
- mCurrPrintAttributes);
+ PrintSpoolerService.peekInstance().setPrintJobAttributesNoPersistence(
+ mPrintJobId, mCurrPrintAttributes);
mMetadata.putBoolean(PrintDocumentAdapter.METADATA_KEY_PRINT_PREVIEW,
!mEditor.isPrintConfirmed());
@@ -378,7 +385,7 @@
final boolean infoChanged = !info.equals(mDocument.info);
if (infoChanged) {
mDocument.info = info;
- PrintSpooler.peekInstance().setPrintJobPrintDocumentInfoNoPersistence(
+ PrintSpoolerService.peekInstance().setPrintJobPrintDocumentInfoNoPersistence(
mPrintJobId, info);
}
@@ -465,12 +472,12 @@
if (Arrays.equals(mDocument.pages, mRequestedPages)) {
// We got a document with exactly the pages we wanted. Hence,
// the printer has to print all pages in the data.
- PrintSpooler.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+ PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
ALL_PAGES_ARRAY);
} else if (Arrays.equals(mDocument.pages, ALL_PAGES_ARRAY)) {
// We requested specific pages but got all of them. Hence,
// the printer has to print only the requested pages.
- PrintSpooler.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+ PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
mRequestedPages);
} else if (PageRangeUtils.contains(mDocument.pages, mRequestedPages)) {
// We requested specific pages and got more but not all pages.
@@ -480,7 +487,7 @@
final int offset = mDocument.pages[0].getStart() - pages[0].getStart();
PageRange[] offsetPages = Arrays.copyOf(mDocument.pages, mDocument.pages.length);
PageRangeUtils.offsetStart(offsetPages, offset);
- PrintSpooler.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+ PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
offsetPages);
} else if (Arrays.equals(mRequestedPages, ALL_PAGES_ARRAY)
&& mDocument.pages.length == 1 && mDocument.pages[0].getStart() == 0
@@ -488,7 +495,7 @@
// We requested all pages via the special constant and got all
// of them as an explicit enumeration. Hence, the printer has
// to print only the requested pages.
- PrintSpooler.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
+ PrintSpoolerService.peekInstance().setPrintJobPagesNoPersistence(mPrintJobId,
mDocument.pages);
} else {
// We did not get the pages we requested, then the application
@@ -500,7 +507,16 @@
}
if (mEditor.isDone()) {
- PrintJobConfigActivity.this.finish();
+ if (mEditor.isPrintingToPdf()) {
+ PrintJobInfo printJob = PrintSpoolerService.peekInstance()
+ .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY);
+ Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+ intent.setType("application/pdf");
+ intent.putExtra(Intent.EXTRA_TITLE, printJob.getLabel());
+ startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
+ } else {
+ PrintJobConfigActivity.this.finish();
+ }
}
}
@@ -607,33 +623,111 @@
}
}
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case ACTIVITY_REQUEST_CREATE_FILE: {
+ if (data != null) {
+ Uri uri = data.getData();
+ writePrintJobDataAndFinish(uri);
+ } else {
+ mEditor.showUi(Editor.UI_EDITING_PRINT_JOB,
+ new Runnable() {
+ @Override
+ public void run() {
+ mEditor.initialize();
+ mEditor.bindUi();
+ mEditor.updateUi();
+ }
+ });
+ }
+ } break;
+
+ case ACTIVITY_REQUEST_SELECT_PRINTER: {
+ if (resultCode == RESULT_OK) {
+ PrinterId printerId = (PrinterId) data.getParcelableExtra(
+ INTENT_EXTRA_PRINTER_ID);
+ // TODO: Make sure the selected printer is in the shown list.
+ mEditor.selectPrinter(printerId);
+ }
+ } break;
+ }
+ }
+
+ private void writePrintJobDataAndFinish(final Uri uri) {
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ PrintJobInfo printJob = PrintSpoolerService.peekInstance()
+ .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY);
+ if (printJob == null) {
+ return null;
+ }
+ File file = PrintSpoolerService.peekInstance()
+ .generateFileForPrintJob(mPrintJobId);
+ in = new FileInputStream(file);
+ out = getContentResolver().openOutputStream(uri);
+ final byte[] buffer = new byte[8192];
+ while (true) {
+ final int readByteCount = in.read(buffer);
+ if (readByteCount < 0) {
+ break;
+ }
+ out.write(buffer, 0, readByteCount);
+ }
+ } catch (FileNotFoundException fnfe) {
+ Log.e(LOG_TAG, "Error writing print job data!", fnfe);
+ } catch (IOException ioe) {
+ Log.e(LOG_TAG, "Error writing print job data!", ioe);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ }
+ return null;
+ }
+
+ @Override
+ public void onPostExecute(Void result) {
+ mEditor.cancel();
+ PrintJobConfigActivity.this.finish();
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+
private final class Editor {
- private final EditText mCopiesEditText;
+ private static final int UI_NONE = 0;
+ private static final int UI_EDITING_PRINT_JOB = 1;
+ private static final int UI_GENERATING_PRINT_JOB = 2;
- private final TextView mRangeTitle;
- private final EditText mRangeEditText;
+ private EditText mCopiesEditText;
- private final Spinner mDestinationSpinner;
+ private TextView mRangeTitle;
+ private EditText mRangeEditText;
+
+ private Spinner mDestinationSpinner;
private final DestinationAdapter mDestinationSpinnerAdapter;
- private final Spinner mMediaSizeSpinner;
+ private Spinner mMediaSizeSpinner;
private final ArrayAdapter<SpinnerItem<MediaSize>> mMediaSizeSpinnerAdapter;
- private final Spinner mColorModeSpinner;
+ private Spinner mColorModeSpinner;
private final ArrayAdapter<SpinnerItem<Integer>> mColorModeSpinnerAdapter;
- private final Spinner mOrientationSpinner;
+ private Spinner mOrientationSpinner;
private final ArrayAdapter<SpinnerItem<Integer>> mOrientationSpinnerAdapter;
- private final Spinner mRangeOptionsSpinner;
+ private Spinner mRangeOptionsSpinner;
private final ArrayAdapter<SpinnerItem<Integer>> mRangeOptionsSpinnerAdapter;
private final SimpleStringSplitter mStringCommaSplitter =
new SimpleStringSplitter(',');
- private final View mContentContainer;
+ private View mContentContainer;
- private final Button mPrintButton;
+ private Button mPrintButton;
private final OnItemSelectedListener mOnItemSelectedListener =
new AdapterView.OnItemSelectedListener() {
@@ -644,18 +738,31 @@
mIgnoreNextDestinationChange = false;
return;
}
+ if (id == DEST_ADAPTER_ITEM_ID_ALL_PRINTERS) {
+ mIgnoreNextDestinationChange = true;
+ mDestinationSpinner.setSelection(0);
+ Intent intent = new Intent(PrintJobConfigActivity.this,
+ SelectPrinterActivity.class);
+ startActivityForResult(intent, ACTIVITY_REQUEST_SELECT_PRINTER);
+ return;
+ }
+ mWaitingForPrinterCapabilities = false;
mCurrPrintAttributes.clear();
PrinterInfo printer = (PrinterInfo) mDestinationSpinnerAdapter
.getItem(position);
if (printer != null) {
- PrintSpooler.peekInstance().setPrintJobPrinterNoPersistence(
+ PrintSpoolerService.peekInstance().setPrintJobPrinterNoPersistence(
mPrintJobId, printer);
PrinterCapabilitiesInfo capabilities = printer.getCapabilities();
if (capabilities == null) {
List<PrinterId> printerIds = new ArrayList<PrinterId>();
printerIds.add(printer.getId());
- final int index = mAvailablePrinters.getItemIndex(printer);
- mAvailablePrinters.refreshItem(index);
+ FusedPrintersProvider printersLoader = (FusedPrintersProvider)
+ (Loader<?>) getLoaderManager().getLoader(
+ LOADER_ID_PRINTERS_LOADER);
+ if (printersLoader != null) {
+ printersLoader.refreshPrinter(printer.getId());
+ }
mWaitingForPrinterCapabilities = true;
//TODO: We need a timeout for the update.
} else {
@@ -668,6 +775,31 @@
}
}
}
+
+ // The printer changed so we want to start with a clean slate
+ // for the print options and let them be populated from the
+ // printer capabilities and use the printer defaults.
+ if (!mMediaSizeSpinnerAdapter.isEmpty()) {
+ mIgnoreNextMediaSizeChange = true;
+ mMediaSizeSpinnerAdapter.clear();
+ }
+ if (!mColorModeSpinnerAdapter.isEmpty()) {
+ mIgnoreNextColorModeChange = true;
+ mColorModeSpinnerAdapter.clear();
+ }
+ if (!mOrientationSpinnerAdapter.isEmpty()) {
+ mIgnoreNextOrientationChange = true;
+ mOrientationSpinnerAdapter.clear();
+ }
+ if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
+ mIgnoreNextRangeOptionChange = true;
+ mRangeOptionsSpinner.setSelection(0);
+ }
+ if (!TextUtils.isEmpty(mCopiesEditText.getText())) {
+ mIgnoreNextCopiesChange = true;
+ mCopiesEditText.setText(MIN_COPIES_STRING);
+ }
+
updateUi();
} else if (spinner == mMediaSizeSpinner) {
if (mIgnoreNextMediaSizeChange) {
@@ -753,7 +885,8 @@
}
mCopiesEditText.setError(null);
- PrintSpooler.peekInstance().setPrintJobCopiesNoPersistence(mPrintJobId, copies);
+ PrintSpoolerService.peekInstance().setPrintJobCopiesNoPersistence(
+ mPrintJobId, copies);
updateUi();
if (hadErrors && !hasErrors() && printAttributesChanged()) {
@@ -832,22 +965,25 @@
private boolean mWaitingForPrinterCapabilities;
+ private int mCurrentUi = UI_NONE;
+
public Editor() {
- // Content container
- mContentContainer = findViewById(R.id.content_container);
-
- // Copies
- mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
- mCopiesEditText.setText(String.valueOf(MIN_COPIES));
- PrintSpooler.peekInstance().setPrintJobCopiesNoPersistence(mPrintJobId, MIN_COPIES);
- mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
- mCopiesEditText.selectAll();
-
// Destination.
- mDestinationSpinnerAdapter = new DestinationAdapter(mAvailablePrinters);
+ mDestinationSpinnerAdapter = new DestinationAdapter();
mDestinationSpinnerAdapter.registerDataSetObserver(new DataSetObserver() {
@Override
public void onChanged() {
+ final int selectedPosition = mDestinationSpinner.getSelectedItemPosition();
+ if (mDestinationSpinnerAdapter.getCount() > 0) {
+ // Make sure we select the first printer if we have data.
+ if (selectedPosition == AdapterView.INVALID_POSITION) {
+ mDestinationSpinner.setSelection(0);
+ }
+ } else {
+ // Make sure we select no printer if we have no data.
+ mDestinationSpinner.setSelection(AdapterView.INVALID_POSITION);
+ }
+
// Maybe we did not have capabilities when the current printer was
// selected, but now the selected printer has capabilities. Generate
// a fake selection so the code in the selection change handling takes
@@ -855,13 +991,9 @@
if (mWaitingForPrinterCapabilities) {
mWaitingForPrinterCapabilities = false;
PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
- if (printer != null) {
- if (printer.getCapabilities() != null) {
- final int selectedPosition =
- mDestinationSpinner.getSelectedItemPosition();
- mOnItemSelectedListener.onItemSelected(mDestinationSpinner, null,
- selectedPosition, selectedPosition);
- }
+ if (printer != null && printer.getCapabilities() != null) {
+ mOnItemSelectedListener.onItemSelected(mDestinationSpinner, null,
+ selectedPosition, selectedPosition);
}
}
updateUi();
@@ -872,41 +1004,23 @@
updateUi();
}
});
- mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
- mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
- mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
// Media size.
- mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner);
mMediaSizeSpinnerAdapter = new ArrayAdapter<SpinnerItem<MediaSize>>(
PrintJobConfigActivity.this,
R.layout.spinner_dropdown_item, R.id.title);
- mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
- mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
// Color mode.
- mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner);
mColorModeSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
PrintJobConfigActivity.this,
R.layout.spinner_dropdown_item, R.id.title);
- mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
- mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
// Orientation
- mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
mOrientationSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
PrintJobConfigActivity.this,
R.layout.spinner_dropdown_item, R.id.title);
- mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
- mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
-
- // Range
- mRangeTitle = (TextView) findViewById(R.id.page_range_title);
- mRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
- mRangeEditText.addTextChangedListener(mRangeTextWatcher);
// Range options
- mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner);
mRangeOptionsSpinnerAdapter = new ArrayAdapter<SpinnerItem<Integer>>(
PrintJobConfigActivity.this,
R.layout.spinner_dropdown_item, R.id.title);
@@ -919,26 +1033,28 @@
mRangeOptionsSpinnerAdapter.add(new SpinnerItem<Integer>(
rangeOptionsValues[i], rangeOptionsLabels[i]));
}
- mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter);
- if (mRangeOptionsSpinner.getSelectedItemPosition() != 0) {
- mIgnoreNextRangeOptionChange = true;
- mRangeOptionsSpinner.setSelection(0);
- }
- // Print button
- mPrintButton = (Button) findViewById(R.id.print_button);
- mPrintButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mEditor.confirmPrint();
- mController.update();
- showGeneratingPrintJobUi();
- }
- });
-
+ showUi(UI_EDITING_PRINT_JOB, null);
+ bindUi();
updateUi();
}
+ public void selectPrinter(PrinterId printerId) {
+ final int printerCount = mDestinationSpinnerAdapter.getCount();
+ for (int i = 0; i < printerCount; i++) {
+ PrinterInfo printer = (PrinterInfo) mDestinationSpinnerAdapter.getItem(i);
+ if (printer.getId().equals(printerId)) {
+ mDestinationSpinner.setSelection(i);
+ return;
+ }
+ }
+ }
+
+ public boolean isPrintingToPdf() {
+ return mDestinationSpinner.getSelectedItem()
+ == mDestinationSpinnerAdapter.mFakePdfPrinter;
+ }
+
public boolean shouldCloseOnTouch(MotionEvent event) {
if (event.getAction() != MotionEvent.ACTION_DOWN) {
return false;
@@ -966,19 +1082,104 @@
}
public boolean isShwoingGeneratingPrintJobUi() {
- return (findViewById(R.id.content_generating) != null);
+ return (mCurrentUi == UI_GENERATING_PRINT_JOB);
}
- private void showGeneratingPrintJobUi() {
- // Find everything we will shuffle around.
- final ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container);
- final View contentEditing = contentContainer.findViewById(R.id.content_editing);
- final View contentGenerating = getLayoutInflater().inflate(
- R.layout.print_job_config_activity_content_generating,
- contentContainer, false);
+ public void showUi(int ui, final Runnable postSwitchCallback) {
+ if (ui == UI_NONE) {
+ throw new IllegalStateException("cannot remove the ui");
+ }
- // Wire the cancel action.
- Button cancelButton = (Button) contentGenerating.findViewById(R.id.cancel_button);
+ if (mCurrentUi == ui) {
+ return;
+ }
+
+ switch (mCurrentUi) {
+ case UI_NONE: {
+ switch (ui) {
+ case UI_EDITING_PRINT_JOB: {
+ doUiSwitch(R.layout.print_job_config_activity_content_editing);
+ registerPrintButtonClickListener();
+ if (postSwitchCallback != null) {
+ postSwitchCallback.run();
+ }
+ } break;
+
+ case UI_GENERATING_PRINT_JOB: {
+ doUiSwitch(R.layout.print_job_config_activity_content_generating);
+ registerCancelButtonClickListener();
+ if (postSwitchCallback != null) {
+ postSwitchCallback.run();
+ }
+ } break;
+ }
+ } break;
+
+ case UI_EDITING_PRINT_JOB: {
+ switch (ui) {
+ case UI_GENERATING_PRINT_JOB: {
+ animateUiSwitch(R.layout.print_job_config_activity_content_generating,
+ new Runnable() {
+ @Override
+ public void run() {
+ registerCancelButtonClickListener();
+ if (postSwitchCallback != null) {
+ postSwitchCallback.run();
+ }
+ }
+ });
+ } break;
+ }
+ } break;
+
+ case UI_GENERATING_PRINT_JOB: {
+ switch (ui) {
+ case UI_EDITING_PRINT_JOB: {
+ animateUiSwitch(R.layout.print_job_config_activity_content_editing,
+ new Runnable() {
+ @Override
+ public void run() {
+ registerPrintButtonClickListener();
+ if (postSwitchCallback != null) {
+ postSwitchCallback.run();
+ }
+ }
+ });
+ } break;
+ }
+ } break;
+ }
+
+ mCurrentUi = ui;
+ }
+
+ private void registerPrintButtonClickListener() {
+ Button printButton = (Button) findViewById(R.id.print_button);
+ printButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
+ if (printer != null) {
+ mEditor.confirmPrint();
+ mController.update();
+ if (!printer.equals(mDestinationSpinnerAdapter.mFakePdfPrinter)) {
+ FusedPrintersProvider printersLoader = (FusedPrintersProvider)
+ (Loader<?>) getLoaderManager().getLoader(
+ LOADER_ID_PRINTERS_LOADER);
+ if (printersLoader != null) {
+ printersLoader.addHistoricalPrinter(printer);
+ }
+ }
+ } else {
+ mEditor.cancel();
+ PrintJobConfigActivity.this.finish();
+ }
+ }
+ });
+ }
+
+ private void registerCancelButtonClickListener() {
+ Button cancelButton = (Button) findViewById(R.id.cancel_button);
cancelButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
@@ -988,24 +1189,38 @@
mEditor.cancel();
}
});
+ }
+
+ private void doUiSwitch(int showLayoutId) {
+ ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container);
+ contentContainer.removeAllViews();
+ getLayoutInflater().inflate(showLayoutId, contentContainer, true);
+ }
+
+ private void animateUiSwitch(int showLayoutId, final Runnable postAnimateCommand) {
+ // Find everything we will shuffle around.
+ final ViewGroup contentContainer = (ViewGroup) findViewById(R.id.content_container);
+ final View hidingView = contentContainer.getChildAt(0);
+ final View showingView = getLayoutInflater().inflate(showLayoutId,
+ null, false);
// First animation - fade out the old content.
- contentEditing.animate().alpha(0.0f).withLayer().withEndAction(new Runnable() {
+ hidingView.animate().alpha(0.0f).withLayer().withEndAction(new Runnable() {
@Override
public void run() {
- contentEditing.setVisibility(View.INVISIBLE);
+ hidingView.setVisibility(View.INVISIBLE);
// Prepare the new content with correct size and alpha.
- contentGenerating.setMinimumWidth(contentContainer.getWidth());
- contentGenerating.setAlpha(0.0f);
+ showingView.setMinimumWidth(contentContainer.getWidth());
+ showingView.setAlpha(0.0f);
- // Compute how to much shrink the container to fit around the new content.
+ // Compute how to much shrink /stretch the content.
final int widthSpec = MeasureSpec.makeMeasureSpec(
- contentContainer.getWidth(), MeasureSpec.AT_MOST);
+ contentContainer.getWidth(), MeasureSpec.UNSPECIFIED);
final int heightSpec = MeasureSpec.makeMeasureSpec(
- contentContainer.getHeight(), MeasureSpec.AT_MOST);
- contentGenerating.measure(widthSpec, heightSpec);
- final float scaleY = (float) contentGenerating.getMeasuredHeight()
+ contentContainer.getHeight(), MeasureSpec.UNSPECIFIED);
+ showingView.measure(widthSpec, heightSpec);
+ final float scaleY = (float) showingView.getMeasuredHeight()
/ (float) contentContainer.getHeight();
// Second animation - resize the container.
@@ -1016,10 +1231,16 @@
// Swap the old and the new content.
contentContainer.removeAllViews();
contentContainer.setScaleY(1.0f);
- contentContainer.addView(contentGenerating);
+ contentContainer.addView(showingView);
// Third animation - show the new content.
- contentGenerating.animate().withLayer().alpha(1.0f);
+ showingView.animate().withLayer().alpha(1.0f).withEndAction(
+ new Runnable() {
+ @Override
+ public void run() {
+ postAnimateCommand.run();
+ }
+ });
}
});
}
@@ -1028,10 +1249,6 @@
public void initialize() {
mEditorState = EDITOR_STATE_INITIALIZED;
- if (mDestinationSpinner.getSelectedItemPosition() != AdapterView.INVALID_POSITION) {
- mIgnoreNextDestinationChange = true;
- mDestinationSpinner.setSelection(AdapterView.INVALID_POSITION);
- }
}
public boolean isCancelled() {
@@ -1054,11 +1271,7 @@
public void confirmPrint() {
mEditorState = EDITOR_STATE_CONFIRMED_PRINT;
- PrinterInfo printer = (PrinterInfo) mDestinationSpinner.getSelectedItem();
- if (printer != null) {
- mFavoritePrinters.addPrinter(printer);
- }
- updateUi();
+ showUi(UI_GENERATING_PRINT_JOB, null);
}
public boolean isPreviewConfirmed() {
@@ -1104,7 +1317,79 @@
return ALL_PAGES_ARRAY;
}
+ private void bindUi() {
+ if (mCurrentUi != UI_EDITING_PRINT_JOB) {
+ return;
+ }
+
+ // Content container
+ mContentContainer = findViewById(R.id.content_container);
+
+ // Copies
+ mCopiesEditText = (EditText) findViewById(R.id.copies_edittext);
+ mCopiesEditText.setText(MIN_COPIES_STRING);
+ mCopiesEditText.addTextChangedListener(mCopiesTextWatcher);
+ mCopiesEditText.selectAll();
+ if (!TextUtils.equals(mCopiesEditText.getText(), MIN_COPIES_STRING)) {
+ mIgnoreNextCopiesChange = true;
+ }
+ PrintSpoolerService.peekInstance().setPrintJobCopiesNoPersistence(
+ mPrintJobId, MIN_COPIES);
+
+ // Destination.
+ mDestinationSpinner = (Spinner) findViewById(R.id.destination_spinner);
+ mDestinationSpinner.setAdapter(mDestinationSpinnerAdapter);
+ mDestinationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+ if (mDestinationSpinnerAdapter.getCount() > 0) {
+ mIgnoreNextDestinationChange = true;
+ }
+
+ // Media size.
+ mMediaSizeSpinner = (Spinner) findViewById(R.id.paper_size_spinner);
+ mMediaSizeSpinner.setAdapter(mMediaSizeSpinnerAdapter);
+ mMediaSizeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+ if (mMediaSizeSpinnerAdapter.getCount() > 0) {
+ mIgnoreNextMediaSizeChange = true;
+ }
+
+ // Color mode.
+ mColorModeSpinner = (Spinner) findViewById(R.id.color_spinner);
+ mColorModeSpinner.setAdapter(mColorModeSpinnerAdapter);
+ mColorModeSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+ if (mColorModeSpinnerAdapter.getCount() > 0) {
+ mIgnoreNextColorModeChange = true;
+ }
+
+ // Orientation
+ mOrientationSpinner = (Spinner) findViewById(R.id.orientation_spinner);
+ mOrientationSpinner.setAdapter(mOrientationSpinnerAdapter);
+ mOrientationSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+ if (mOrientationSpinnerAdapter.getCount() > 0) {
+ mIgnoreNextOrientationChange = true;
+ }
+
+ // Range
+ mRangeTitle = (TextView) findViewById(R.id.page_range_title);
+ mRangeEditText = (EditText) findViewById(R.id.page_range_edittext);
+ mRangeEditText.addTextChangedListener(mRangeTextWatcher);
+
+ // Range options
+ mRangeOptionsSpinner = (Spinner) findViewById(R.id.range_options_spinner);
+ mRangeOptionsSpinner.setAdapter(mRangeOptionsSpinnerAdapter);
+ mRangeOptionsSpinner.setOnItemSelectedListener(mOnItemSelectedListener);
+ if (mRangeOptionsSpinnerAdapter.getCount() > 0) {
+ mIgnoreNextRangeOptionChange = true;
+ }
+
+ // Print button
+ mPrintButton = (Button) findViewById(R.id.print_button);
+ registerPrintButtonClickListener();
+ }
+
public void updateUi() {
+ if (mCurrentUi != UI_EDITING_PRINT_JOB) {
+ return;
+ }
if (isPrintConfirmed() || isPreviewConfirmed() || isCancelled()) {
mDestinationSpinner.setEnabled(false);
mCopiesEditText.setEnabled(false);
@@ -1119,14 +1404,20 @@
return;
}
+ // If a printer with capabilities is selected, then we enabled all options.
+ boolean allOptionsEnabled = false;
final int selectedIndex = mDestinationSpinner.getSelectedItemPosition();
+ if (selectedIndex >= 0) {
+ Object item = mDestinationSpinnerAdapter.getItem(selectedIndex);
+ if (item instanceof PrinterInfo) {
+ PrinterInfo printer = (PrinterInfo) item;
+ if (printer.getCapabilities() != null) {
+ allOptionsEnabled = true;
+ }
+ }
+ }
- if (selectedIndex < 0 || ((PrinterInfo) mDestinationSpinnerAdapter.getItem(
- selectedIndex)).getCapabilities() == null) {
-
- // Destination
- mDestinationSpinner.setEnabled(false);
-
+ if (!allOptionsEnabled) {
String minCopiesString = String.valueOf(MIN_COPIES);
if (!TextUtils.equals(mCopiesEditText.getText(), minCopiesString)) {
mIgnoreNextCopiesChange = true;
@@ -1183,9 +1474,6 @@
PrinterCapabilitiesInfo capabilities = printer.getCapabilities();
printer.getCapabilities().getDefaults(defaultAttributes);
- // Destination
- mDestinationSpinner.setEnabled(true);
-
// Copies
mCopiesEditText.setEnabled(true);
@@ -1223,6 +1511,7 @@
}
}
}
+ mMediaSizeSpinner.setEnabled(true);
// Color mode.
final int colorModes = capabilities.getColorModes();
@@ -1271,6 +1560,7 @@
}
}
}
+ mColorModeSpinner.setEnabled(true);
// Orientation.
final int orientations = capabilities.getOrientations();
@@ -1321,20 +1611,25 @@
}
}
}
+ mOrientationSpinner.setEnabled(true);
// Range options
PrintDocumentInfo info = mDocument.info;
if (info != null && (info.getPageCount() > 1
|| info.getPageCount() == PrintDocumentInfo.PAGE_COUNT_UNKNOWN)) {
mRangeOptionsSpinner.setEnabled(true);
- if (mRangeOptionsSpinner.getSelectedItemPosition() > 0
- && !mRangeEditText.isEnabled()) {
- mRangeEditText.setEnabled(true);
- mRangeEditText.setVisibility(View.VISIBLE);
- mRangeEditText.requestFocus();
- InputMethodManager imm = (InputMethodManager)
- getSystemService(INPUT_METHOD_SERVICE);
- imm.showSoftInput(mRangeEditText, 0);
+ if (mRangeOptionsSpinner.getSelectedItemPosition() > 0) {
+ if (!mRangeEditText.isEnabled()) {
+ mRangeEditText.setEnabled(true);
+ mRangeEditText.setVisibility(View.VISIBLE);
+ mRangeEditText.requestFocus();
+ InputMethodManager imm = (InputMethodManager)
+ getSystemService(INPUT_METHOD_SERVICE);
+ imm.showSoftInput(mRangeEditText, 0);
+ }
+ } else {
+ mRangeEditText.setEnabled(false);
+ mRangeEditText.setVisibility(View.INVISIBLE);
}
final int pageCount = mDocument.info.getPageCount();
mRangeTitle.setText(getString(R.string.label_pages,
@@ -1352,6 +1647,7 @@
mRangeEditText.setEnabled(false);
mRangeEditText.setVisibility(View.INVISIBLE);
}
+ mRangeOptionsSpinner.setEnabled(true);
// Print/Print preview
if ((mRangeOptionsSpinner.getSelectedItemPosition() == 1
@@ -1378,6 +1674,7 @@
mCopiesEditText.selectAll();
mCopiesEditText.requestFocus();
}
+ mCopiesEditText.setEnabled(true);
}
}
@@ -1407,38 +1704,51 @@
}
}
- private final class DestinationAdapter extends BaseAdapter {
- private final AvailablePrinterProvider mProvider;
+ private final class DestinationAdapter extends BaseAdapter
+ implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>{
+ private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
- private final DataSetObserver mObserver = new DataSetObserver() {
- @Override
- public void onChanged() {
- notifyDataSetChanged();
- }
+ public final PrinterInfo mFakePdfPrinter;
- @Override
- public void onInvalidated() {
- notifyDataSetInvalidated();
- }
- };
-
- public DestinationAdapter(AvailablePrinterProvider provider) {
- mProvider = provider;
- mProvider.registerObserver(mObserver);
+ public DestinationAdapter() {
+ getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
+ mFakePdfPrinter = createFakePdfPrinter();
}
@Override
public int getCount() {
- return mProvider.getItemCount();
+ return Math.max(Math.min(mPrinters.size(), DEST_ADAPTER_MAX_ITEM_COUNT),
+ DEST_ADAPTER_MIN_ITEM_COUNT);
}
@Override
public Object getItem(int position) {
- return mProvider.getItemAt(position);
+ if (position == DEST_ADAPTER_POSITION_SAVE_AS_PDF) {
+ return mFakePdfPrinter;
+ }
+ if (!mPrinters.isEmpty()) {
+ if (position < DEST_ADAPTER_POSITION_SAVE_AS_PDF) {
+ return mPrinters.get(position);
+ } else if (position > DEST_ADAPTER_POSITION_SAVE_AS_PDF
+ && position < getCount() - 1) {
+ return mPrinters.get(position - 1);
+ }
+ }
+ return null;
}
@Override
public long getItemId(int position) {
+ if (position == DEST_ADAPTER_POSITION_SAVE_AS_PDF) {
+ return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF;
+ }
+ if (mPrinters.isEmpty()) {
+ if (position == DEST_ADAPTER_POSITION_SEARCHING_FOR_PRINTERS) {
+ return DEST_ADAPTER_ITEM_ID_SEARCHING_FOR_PRINTERS;
+ }
+ } else if (position == getCount() - 1) {
+ return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS;
+ }
return position;
}
@@ -1455,24 +1765,92 @@
R.layout.spinner_dropdown_item, parent, false);
}
- PrinterInfo printerInfo = mProvider.getItemAt(position);
- TextView title = (TextView) convertView.findViewById(R.id.title);
- title.setText(printerInfo.getName());
+ CharSequence title = null;
+ CharSequence subtitle = null;
- try {
- TextView subtitle = (TextView)
- convertView.findViewById(R.id.subtitle);
- PackageManager pm = getPackageManager();
- PackageInfo packageInfo = pm.getPackageInfo(
- printerInfo.getId().getServiceName().getPackageName(), 0);
- subtitle.setText(packageInfo.applicationInfo.loadLabel(pm));
- subtitle.setVisibility(View.VISIBLE);
- } catch (NameNotFoundException nnfe) {
- /* ignore */
+ if (mPrinters.isEmpty()
+ && position == DEST_ADAPTER_POSITION_SEARCHING_FOR_PRINTERS) {
+ title = getString(R.string.searching_for_printers);
+ } else {
+ if (position == DEST_ADAPTER_POSITION_SAVE_AS_PDF) {
+ PrinterInfo printer = (PrinterInfo) getItem(position);
+ title = printer.getName();
+ } else if (position == getCount() - 1) {
+ title = getString(R.string.all_printers);
+ } else {
+ PrinterInfo printer = (PrinterInfo) getItem(position);
+ title = printer.getName();
+ try {
+ PackageInfo packageInfo = getPackageManager().getPackageInfo(
+ printer.getId().getServiceName().getPackageName(), 0);
+ subtitle = packageInfo.applicationInfo.loadLabel(getPackageManager());
+ } catch (NameNotFoundException nnfe) {
+ /* ignore */
+ }
+ }
+ }
+
+ TextView titleView = (TextView) convertView.findViewById(R.id.title);
+ titleView.setText(title);
+
+ TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
+ if (!TextUtils.isEmpty(subtitle)) {
+ subtitleView.setText(subtitle);
+ subtitleView.setVisibility(View.VISIBLE);
+ } else {
+ subtitleView.setText(null);
+ subtitleView.setVisibility(View.GONE);
}
return convertView;
}
+
+ @Override
+ public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
+ if (id == LOADER_ID_PRINTERS_LOADER) {
+ return new FusedPrintersProvider(PrintJobConfigActivity.this);
+ }
+ return null;
+ }
+
+ @Override
+ public void onLoadFinished(Loader<List<PrinterInfo>> loader,
+ List<PrinterInfo> printers) {
+ mPrinters.clear();
+ mPrinters.addAll(printers);
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void onLoaderReset(Loader<List<PrinterInfo>> loader) {
+ mPrinters.clear();
+ notifyDataSetInvalidated();
+ }
+
+ private PrinterInfo createFakePdfPrinter() {
+ PrinterId printerId = new PrinterId(getComponentName(), "PDF printer");
+
+ PrinterCapabilitiesInfo capabilities =
+ new PrinterCapabilitiesInfo.Builder(printerId)
+ .addMediaSize(MediaSize.createMediaSize(getPackageManager(),
+ MediaSize.ISO_A4), true)
+ .addMediaSize(MediaSize.createMediaSize(getPackageManager(),
+ MediaSize.NA_LETTER), false)
+ .addResolution(new Resolution("PDF resolution", "PDF resolution",
+ 300, 300), true)
+ .setColorModes(PrintAttributes.COLOR_MODE_COLOR
+ | PrintAttributes.COLOR_MODE_MONOCHROME,
+ PrintAttributes.COLOR_MODE_COLOR)
+ .setOrientations(PrintAttributes.ORIENTATION_PORTRAIT
+ | PrintAttributes.ORIENTATION_LANDSCAPE,
+ PrintAttributes.ORIENTATION_PORTRAIT)
+ .create();
+
+ return new PrinterInfo.Builder(printerId, getString(R.string.save_as_pdf),
+ PrinterInfo.STATUS_READY)
+ .setCapabilities(capabilities)
+ .create();
+ }
}
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
deleted file mode 100644
index c2cf65e..0000000
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpooler.java
+++ /dev/null
@@ -1,969 +0,0 @@
-/*
- * Copyright (C) 2013 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.printspooler;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.AsyncTask;
-import android.os.ParcelFileDescriptor;
-import android.print.IPrintClient;
-import android.print.IPrinterDiscoverySessionObserver;
-import android.print.PageRange;
-import android.print.PrintAttributes;
-import android.print.PrintAttributes.Margins;
-import android.print.PrintAttributes.MediaSize;
-import android.print.PrintAttributes.Resolution;
-import android.print.PrintAttributes.Tray;
-import android.print.PrintDocumentInfo;
-import android.print.PrintJobInfo;
-import android.print.PrintManager;
-import android.print.PrinterId;
-import android.print.PrinterInfo;
-import android.util.AtomicFile;
-import android.util.Log;
-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;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-public class PrintSpooler {
-
- private static final String LOG_TAG = "PrintSpooler";
-
- private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = true;
-
- private static final boolean DEBUG_PERSISTENCE = true;
-
- private static final boolean PERSISTNECE_MANAGER_ENABLED = true;
-
- private static final String PRINT_FILE_EXTENSION = "pdf";
-
- private static int sPrintJobIdCounter;
-
- private static final Object sLock = new Object();
-
- private static PrintSpooler sInstance;
-
- private final Object mLock = new Object();
-
- private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
-
- private final PersistenceManager mPersistanceManager;
-
- private final NotificationController mNotificationController;
-
- private final PrintSpoolerService mService;
-
- public static void destroyInstance() {
- synchronized (sLock) {
- sInstance = null;
- }
- }
-
- public static void createInstance(PrintSpoolerService service) {
- synchronized (sLock) {
- sInstance = new PrintSpooler(service);
- }
- }
-
- public static PrintSpooler peekInstance() {
- synchronized (sLock) {
- return sInstance;
- }
- }
-
- private PrintSpooler(PrintSpoolerService service) {
- mService = service;
- mPersistanceManager = new PersistenceManager(service);
- mNotificationController = new NotificationController(service);
- synchronized (mLock) {
- mPersistanceManager.readStateLocked();
- handleReadPrintJobsLocked();
- }
- }
-
- public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName,
- int state, int appId) {
- List<PrintJobInfo> foundPrintJobs = null;
- synchronized (mLock) {
- final int printJobCount = mPrintJobs.size();
- for (int i = 0; i < printJobCount; i++) {
- PrintJobInfo printJob = mPrintJobs.get(i);
- PrinterId printerId = printJob.getPrinterId();
- final boolean sameComponent = (componentName == null
- || (printerId != null
- && componentName.equals(printerId.getServiceName())));
- final boolean sameAppId = appId == PrintManager.APP_ID_ANY
- || printJob.getAppId() == appId;
- final boolean sameState = (state == printJob.getState())
- || (state == PrintJobInfo.STATE_ANY)
- || (state == PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS
- && printJob.getState() > PrintJobInfo.STATE_CREATED);
- if (sameComponent && sameAppId && sameState) {
- if (foundPrintJobs == null) {
- foundPrintJobs = new ArrayList<PrintJobInfo>();
- }
- foundPrintJobs.add(printJob);
- }
- }
- }
- return foundPrintJobs;
- }
-
- public PrintJobInfo getPrintJobInfo(int printJobId, int appId) {
- synchronized (mLock) {
- final int printJobCount = mPrintJobs.size();
- for (int i = 0; i < printJobCount; i++) {
- PrintJobInfo printJob = mPrintJobs.get(i);
- if (printJob.getId() == printJobId
- && (appId == PrintManager.APP_ID_ANY
- || appId == printJob.getAppId())) {
- return printJob;
- }
- }
- return null;
- }
- }
-
- public PrintJobInfo createPrintJob(CharSequence label, IPrintClient client,
- PrintAttributes attributes, int appId) {
- synchronized (mLock) {
- final int printJobId = generatePrintJobIdLocked();
- PrintJobInfo printJob = new PrintJobInfo();
- printJob.setId(printJobId);
- printJob.setAppId(appId);
- printJob.setLabel(label);
- printJob.setAttributes(attributes);
- printJob.setState(PrintJobInfo.STATE_CREATED);
-
- addPrintJobLocked(printJob);
-
- return printJob;
- }
- }
-
- private void handleReadPrintJobsLocked() {
- final int printJobCount = mPrintJobs.size();
- for (int i = 0; i < printJobCount; i++) {
- PrintJobInfo printJob = mPrintJobs.get(i);
-
- // Update the notification.
- mNotificationController.onPrintJobStateChanged(printJob);
-
- switch (printJob.getState()) {
- case PrintJobInfo.STATE_QUEUED:
- case PrintJobInfo.STATE_STARTED: {
- // We have a print job that was queued or started in the past
- // but the device battery died or a crash occurred. In this case
- // we assume the print job failed and let the user decide whether
- // to restart the job or just
- setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
- mService.getString(R.string.no_connection_to_printer));
- } break;
- }
- }
- }
-
- public void checkAllPrintJobsHandled() {
- synchronized (mLock) {
- if (!hasActivePrintJobsLocked()) {
- notifyOnAllPrintJobsHandled();
- }
- }
- }
-
- public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
- mService.createPrinterDiscoverySession(observer);
- }
-
- private int generatePrintJobIdLocked() {
- int printJobId = sPrintJobIdCounter++;
- while (isDuplicatePrintJobId(printJobId)) {
- printJobId = sPrintJobIdCounter++;
- }
- return printJobId;
- }
-
- private boolean isDuplicatePrintJobId(int printJobId) {
- final int printJobCount = mPrintJobs.size();
- for (int j = 0; j < printJobCount; j++) {
- PrintJobInfo printJob = mPrintJobs.get(j);
- if (printJob.getId() == printJobId) {
- return true;
- }
- }
- return false;
- }
-
- public void writePrintJobData(final ParcelFileDescriptor fd, final int printJobId) {
- final PrintJobInfo printJob;
- synchronized (mLock) {
- printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
- }
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- FileInputStream in = null;
- FileOutputStream out = null;
- try {
- if (printJob != null) {
- File file = generateFileForPrintJob(printJobId);
- in = new FileInputStream(file);
- out = new FileOutputStream(fd.getFileDescriptor());
- }
- final byte[] buffer = new byte[8192];
- while (true) {
- final int readByteCount = in.read(buffer);
- if (readByteCount < 0) {
- return null;
- }
- out.write(buffer, 0, readByteCount);
- }
- } catch (FileNotFoundException fnfe) {
- Log.e(LOG_TAG, "Error writing print job data!", fnfe);
- } catch (IOException ioe) {
- Log.e(LOG_TAG, "Error writing print job data!", ioe);
- } finally {
- IoUtils.closeQuietly(in);
- IoUtils.closeQuietly(out);
- IoUtils.closeQuietly(fd);
- }
- Log.i(LOG_TAG, "[END WRITE]");
- return null;
- }
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
- }
-
- public File generateFileForPrintJob(int printJobId) {
- return new File(mService.getFilesDir(), "print_job_"
- + printJobId + "." + PRINT_FILE_EXTENSION);
- }
-
- private void addPrintJobLocked(PrintJobInfo printJob) {
- mPrintJobs.add(printJob);
- if (DEBUG_PRINT_JOB_LIFECYCLE) {
- Slog.i(LOG_TAG, "[ADD] " + printJob);
- }
- }
-
- private void removePrintJobLocked(PrintJobInfo printJob) {
- if (mPrintJobs.remove(printJob)) {
- generateFileForPrintJob(printJob.getId()).delete();
- if (DEBUG_PRINT_JOB_LIFECYCLE) {
- Slog.i(LOG_TAG, "[REMOVE] " + printJob);
- }
- }
- }
-
- public boolean setPrintJobState(int printJobId, int state, CharSequence error) {
- boolean success = false;
-
- synchronized (mLock) {
- PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
- if (printJob != null) {
- success = true;
-
- printJob.setState(state);
- printJob.setFailureReason(error);
- mNotificationController.onPrintJobStateChanged(printJob);
-
- if (DEBUG_PRINT_JOB_LIFECYCLE) {
- Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
- }
-
- switch (state) {
- case PrintJobInfo.STATE_COMPLETED:
- case PrintJobInfo.STATE_CANCELED:
- removePrintJobLocked(printJob);
- // $fall-through$
- case PrintJobInfo.STATE_FAILED: {
- PrinterId printerId = printJob.getPrinterId();
- if (printerId != null) {
- ComponentName service = printerId.getServiceName();
- if (!hasActivePrintJobsForServiceLocked(service)) {
- mService.onAllPrintJobsForServiceHandled(service);
- }
- }
- } break;
-
- case PrintJobInfo.STATE_QUEUED: {
- mService.onPrintJobQueued(new PrintJobInfo(printJob));
- } break;
- }
-
- if (shouldPersistPrintJob(printJob)) {
- mPersistanceManager.writeStateLocked();
- }
-
- if (!hasActivePrintJobsLocked()) {
- notifyOnAllPrintJobsHandled();
- }
- }
- }
-
- return success;
- }
-
- public boolean hasActivePrintJobsLocked() {
- final int printJobCount = mPrintJobs.size();
- for (int i = 0; i < printJobCount; i++) {
- PrintJobInfo printJob = mPrintJobs.get(i);
- if (isActiveState(printJob.getState())) {
- return true;
- }
- }
- return false;
- }
-
- public boolean hasActivePrintJobsForServiceLocked(ComponentName service) {
- final int printJobCount = mPrintJobs.size();
- for (int i = 0; i < printJobCount; i++) {
- PrintJobInfo printJob = mPrintJobs.get(i);
- if (isActiveState(printJob.getState())
- && printJob.getPrinterId().getServiceName().equals(service)) {
- return true;
- }
- }
- return false;
- }
-
- private static boolean isActiveState(int printJobState) {
- return printJobState == PrintJobInfo.STATE_CREATED
- || printJobState == PrintJobInfo.STATE_QUEUED
- || printJobState == PrintJobInfo.STATE_STARTED;
- }
-
- public boolean setPrintJobTag(int printJobId, String tag) {
- synchronized (mLock) {
- PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
- if (printJob != null) {
- String printJobTag = printJob.getTag();
- if (printJobTag == null) {
- if (tag == null) {
- return false;
- }
- } else if (printJobTag.equals(tag)) {
- return false;
- }
- printJob.setTag(tag);
- if (shouldPersistPrintJob(printJob)) {
- mPersistanceManager.writeStateLocked();
- }
- return true;
- }
- }
- return false;
- }
-
- public void setPrintJobCopiesNoPersistence(int printJobId, int copies) {
- synchronized (mLock) {
- PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
- if (printJob != null) {
- printJob.setCopies(copies);
- }
- }
- }
-
- public void setPrintJobPrintDocumentInfoNoPersistence(int printJobId, PrintDocumentInfo info) {
- synchronized (mLock) {
- PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
- if (printJob != null) {
- printJob.setDocumentInfo(info);
- }
- }
- }
-
- public void setPrintJobAttributesNoPersistence(int printJobId, PrintAttributes attributes) {
- synchronized (mLock) {
- PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
- if (printJob != null) {
- printJob.setAttributes(attributes);
- }
- }
- }
-
- public void setPrintJobPrinterNoPersistence(int printJobId, PrinterInfo printer) {
- synchronized (mLock) {
- PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
- if (printJob != null) {
- printJob.setPrinterId(printer.getId());
- printJob.setPrinterName(printer.getName());
- }
- }
- }
-
- public void setPrintJobPagesNoPersistence(int printJobId, PageRange[] pages) {
- synchronized (mLock) {
- PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
- if (printJob != null) {
- printJob.setPages(pages);
- }
- }
- }
-
- private boolean shouldPersistPrintJob(PrintJobInfo printJob) {
- return printJob.getState() >= PrintJobInfo.STATE_QUEUED;
- }
-
- private void notifyOnAllPrintJobsHandled() {
- // This has to run on the tread that is persisting the current state
- // since this call may result in the system unbinding from the spooler
- // and as a result the spooler process may get killed before the write
- // completes.
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- mService.onAllPrintJobsHandled();
- return null;
- }
- }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
- }
-
- private final class PersistenceManager {
- private static final String PERSIST_FILE_NAME = "print_spooler_state.xml";
-
- private static final String TAG_SPOOLER = "spooler";
- private static final String TAG_JOB = "job";
-
- private static final String TAG_PRINTER_ID = "printerId";
- private static final String TAG_PAGE_RANGE = "pageRange";
- private static final String TAG_ATTRIBUTES = "attributes";
- private static final String TAG_DOCUMENT_INFO = "documentInfo";
-
- private static final String ATTR_ID = "id";
- private static final String ATTR_LABEL = "label";
- private static final String ATTR_STATE = "state";
- private static final String ATTR_APP_ID = "appId";
- private static final String ATTR_USER_ID = "userId";
- private static final String ATTR_TAG = "tag";
- private static final String ATTR_COPIES = "copies";
-
- private static final String TAG_MEDIA_SIZE = "mediaSize";
- private static final String TAG_RESOLUTION = "resolution";
- private static final String TAG_MARGINS = "margins";
- private static final String TAG_INPUT_TRAY = "inputTray";
- private static final String TAG_OUTPUT_TRAY = "outputTray";
-
- private static final String ATTR_DUPLEX_MODE = "duplexMode";
- private static final String ATTR_COLOR_MODE = "colorMode";
- private static final String ATTR_FITTING_MODE = "fittingMode";
- private static final String ATTR_ORIENTATION = "orientation";
-
- private static final String ATTR_LOCAL_ID = "printerName";
- private static final String ATTR_SERVICE_NAME = "serviceName";
-
- private static final String ATTR_WIDTH_MILS = "widthMils";
- private static final String ATTR_HEIGHT_MILS = "heightMils";
-
- private static final String ATTR_HORIZONTAL_DPI = "horizontalDip";
- private static final String ATTR_VERTICAL_DPI = "verticalDpi";
-
- private static final String ATTR_LEFT_MILS = "leftMils";
- private static final String ATTR_TOP_MILS = "topMils";
- private static final String ATTR_RIGHT_MILS = "rightMils";
- private static final String ATTR_BOTTOM_MILS = "bottomMils";
-
- private static final String ATTR_START = "start";
- private static final String ATTR_END = "end";
-
- private static final String ATTR_NAME = "name";
- private static final String ATTR_PAGE_COUNT = "pageCount";
- private static final String ATTR_CONTENT_TYPE = "contentType";
-
- private final AtomicFile mStatePersistFile;
-
- private boolean mWriteStateScheduled;
-
- private PersistenceManager(Context context) {
- mStatePersistFile = new AtomicFile(new File(context.getFilesDir(),
- PERSIST_FILE_NAME));
- }
-
- public void writeStateLocked() {
- if (!PERSISTNECE_MANAGER_ENABLED) {
- return;
- }
- if (mWriteStateScheduled) {
- return;
- }
- mWriteStateScheduled = true;
- new AsyncTask<Void, Void, Void>() {
- @Override
- protected Void doInBackground(Void... params) {
- synchronized (mLock) {
- mWriteStateScheduled = false;
- doWriteStateLocked();
- }
- return null;
- }
- }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
- }
-
- private void doWriteStateLocked() {
- if (DEBUG_PERSISTENCE) {
- Log.i(LOG_TAG, "[PERSIST START]");
- }
- FileOutputStream out = null;
- try {
- out = mStatePersistFile.startWrite();
-
- XmlSerializer serializer = new FastXmlSerializer();
- serializer.setOutput(out, "utf-8");
- serializer.startDocument(null, true);
- serializer.startTag(null, TAG_SPOOLER);
-
- List<PrintJobInfo> printJobs = mPrintJobs;
-
- final int printJobCount = printJobs.size();
- for (int j = 0; j < printJobCount; j++) {
- PrintJobInfo printJob = printJobs.get(j);
-
- final int state = printJob.getState();
- if (state < PrintJobInfo.STATE_QUEUED
- || state > PrintJobInfo.STATE_CANCELED) {
- continue;
- }
-
- serializer.startTag(null, TAG_JOB);
-
- serializer.attribute(null, ATTR_ID, String.valueOf(printJob.getId()));
- serializer.attribute(null, ATTR_LABEL, printJob.getLabel().toString());
- serializer.attribute(null, ATTR_STATE, String.valueOf(printJob.getState()));
- serializer.attribute(null, ATTR_APP_ID, String.valueOf(printJob.getAppId()));
- serializer.attribute(null, ATTR_USER_ID, String.valueOf(printJob.getUserId()));
- String tag = printJob.getTag();
- if (tag != null) {
- serializer.attribute(null, ATTR_TAG, tag);
- }
- serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies()));
-
- PrinterId printerId = printJob.getPrinterId();
- if (printerId != null) {
- serializer.startTag(null, TAG_PRINTER_ID);
- serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
- serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
- .flattenToString());
- serializer.endTag(null, TAG_PRINTER_ID);
- }
-
- PageRange[] pages = printJob.getPages();
- if (pages != null) {
- for (int i = 0; i < pages.length; i++) {
- serializer.startTag(null, TAG_PAGE_RANGE);
- serializer.attribute(null, ATTR_START, String.valueOf(
- pages[i].getStart()));
- serializer.attribute(null, ATTR_END, String.valueOf(
- pages[i].getEnd()));
- serializer.endTag(null, TAG_PAGE_RANGE);
- }
- }
-
- PrintAttributes attributes = printJob.getAttributes();
- if (attributes != null) {
- serializer.startTag(null, TAG_ATTRIBUTES);
-
- final int duplexMode = attributes.getDuplexMode();
- serializer.attribute(null, ATTR_DUPLEX_MODE,
- String.valueOf(duplexMode));
-
- final int colorMode = attributes.getColorMode();
- serializer.attribute(null, ATTR_COLOR_MODE,
- String.valueOf(colorMode));
-
- final int fittingMode = attributes.getFittingMode();
- serializer.attribute(null, ATTR_FITTING_MODE,
- String.valueOf(fittingMode));
-
- final int orientation = attributes.getOrientation();
- serializer.attribute(null, ATTR_ORIENTATION,
- String.valueOf(orientation));
-
- MediaSize mediaSize = attributes.getMediaSize();
- if (mediaSize != null) {
- serializer.startTag(null, TAG_MEDIA_SIZE);
- serializer.attribute(null, ATTR_ID, mediaSize.getId());
- serializer.attribute(null, ATTR_LABEL, mediaSize.getLabel()
- .toString());
- serializer.attribute(null, ATTR_WIDTH_MILS, String.valueOf(
- mediaSize.getWidthMils()));
- serializer.attribute(null, ATTR_HEIGHT_MILS,String.valueOf(
- mediaSize.getHeightMils()));
- serializer.endTag(null, TAG_MEDIA_SIZE);
- }
-
- Resolution resolution = attributes.getResolution();
- if (resolution != null) {
- serializer.startTag(null, TAG_RESOLUTION);
- serializer.attribute(null, ATTR_ID, resolution.getId());
- serializer.attribute(null, ATTR_LABEL, resolution.getLabel()
- .toString());
- serializer.attribute(null, ATTR_HORIZONTAL_DPI, String.valueOf(
- resolution.getHorizontalDpi()));
- serializer.attribute(null, ATTR_VERTICAL_DPI, String.valueOf(
- resolution.getVerticalDpi()));
- serializer.endTag(null, TAG_RESOLUTION);
- }
-
- Margins margins = attributes.getMargins();
- if (margins != null) {
- serializer.startTag(null, TAG_MARGINS);
- serializer.attribute(null, ATTR_LEFT_MILS, String.valueOf(
- margins.getLeftMils()));
- serializer.attribute(null, ATTR_TOP_MILS, String.valueOf(
- margins.getTopMils()));
- serializer.attribute(null, ATTR_RIGHT_MILS, String.valueOf(
- margins.getRightMils()));
- serializer.attribute(null, ATTR_BOTTOM_MILS, String.valueOf(
- margins.getBottomMils()));
- serializer.endTag(null, TAG_MARGINS);
- }
-
- Tray inputTray = attributes.getInputTray();
- if (inputTray != null) {
- serializer.startTag(null, TAG_INPUT_TRAY);
- serializer.attribute(null, ATTR_ID, inputTray.getId());
- serializer.attribute(null, ATTR_LABEL, inputTray.getLabel()
- .toString());
- serializer.endTag(null, TAG_INPUT_TRAY);
- }
-
- Tray outputTray = attributes.getOutputTray();
- if (outputTray != null) {
- serializer.startTag(null, TAG_OUTPUT_TRAY);
- serializer.attribute(null, ATTR_ID, outputTray.getId());
- serializer.attribute(null, ATTR_LABEL, outputTray.getLabel()
- .toString());
- serializer.endTag(null, TAG_OUTPUT_TRAY);
- }
-
- serializer.endTag(null, TAG_ATTRIBUTES);
- }
-
- PrintDocumentInfo documentInfo = printJob.getDocumentInfo();
- if (documentInfo != null) {
- serializer.startTag(null, TAG_DOCUMENT_INFO);
- serializer.attribute(null, ATTR_NAME, documentInfo.getName());
- serializer.attribute(null, ATTR_CONTENT_TYPE, String.valueOf(
- documentInfo.getContentType()));
- serializer.attribute(null, ATTR_PAGE_COUNT, String.valueOf(
- documentInfo.getPageCount()));
- serializer.endTag(null, TAG_DOCUMENT_INFO);
- }
-
- serializer.endTag(null, TAG_JOB);
-
- if (DEBUG_PERSISTENCE) {
- Log.i(LOG_TAG, "[PERSISTED] " + printJob);
- }
- }
-
- serializer.endTag(null, TAG_SPOOLER);
- serializer.endDocument();
- mStatePersistFile.finishWrite(out);
- if (DEBUG_PERSISTENCE) {
- Log.i(LOG_TAG, "[PERSIST END]");
- }
- } catch (IOException e) {
- Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
- mStatePersistFile.failWrite(out);
- } finally {
- IoUtils.closeQuietly(out);
- }
- }
-
- public void readStateLocked() {
- if (!PERSISTNECE_MANAGER_ENABLED) {
- return;
- }
- FileInputStream in = null;
- try {
- in = mStatePersistFile.openRead();
- } catch (FileNotFoundException e) {
- Log.i(LOG_TAG, "No existing print spooler state.");
- return;
- }
- try {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(in, null);
- parseState(parser);
- } catch (IllegalStateException ise) {
- Slog.w(LOG_TAG, "Failed parsing ", ise);
- } catch (NullPointerException npe) {
- Slog.w(LOG_TAG, "Failed parsing ", npe);
- } catch (NumberFormatException nfe) {
- Slog.w(LOG_TAG, "Failed parsing ", nfe);
- } catch (XmlPullParserException xppe) {
- Slog.w(LOG_TAG, "Failed parsing ", xppe);
- } catch (IOException ioe) {
- Slog.w(LOG_TAG, "Failed parsing ", ioe);
- } catch (IndexOutOfBoundsException iobe) {
- Slog.w(LOG_TAG, "Failed parsing ", iobe);
- } finally {
- IoUtils.closeQuietly(in);
- }
- }
-
- private void parseState(XmlPullParser parser)
- throws IOException, XmlPullParserException {
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.START_TAG, TAG_SPOOLER);
- parser.next();
-
- while (parsePrintJob(parser)) {
- parser.next();
- }
-
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_SPOOLER);
- }
-
- private boolean parsePrintJob(XmlPullParser parser)
- throws IOException, XmlPullParserException {
- skipEmptyTextTags(parser);
- if (!accept(parser, XmlPullParser.START_TAG, TAG_JOB)) {
- return false;
- }
-
- PrintJobInfo printJob = new PrintJobInfo();
-
- final int printJobId = Integer.parseInt(parser.getAttributeValue(null, ATTR_ID));
- printJob.setId(printJobId);
- String label = parser.getAttributeValue(null, ATTR_LABEL);
- printJob.setLabel(label);
- final int state = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATE));
- printJob.setState(state);
- final int appId = Integer.parseInt(parser.getAttributeValue(null, ATTR_APP_ID));
- printJob.setAppId(appId);
- final int userId = Integer.parseInt(parser.getAttributeValue(null, ATTR_USER_ID));
- printJob.setUserId(userId);
- String tag = parser.getAttributeValue(null, ATTR_TAG);
- printJob.setTag(tag);
- String copies = parser.getAttributeValue(null, ATTR_COPIES);
- printJob.setCopies(Integer.parseInt(copies));
-
- parser.next();
-
- skipEmptyTextTags(parser);
- if (accept(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID)) {
- String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
- ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
- null, ATTR_SERVICE_NAME));
- printJob.setPrinterId(new PrinterId(service, localId));
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
- parser.next();
- }
-
- skipEmptyTextTags(parser);
- List<PageRange> pageRanges = null;
- while (accept(parser, XmlPullParser.START_TAG, TAG_PAGE_RANGE)) {
- final int start = Integer.parseInt(parser.getAttributeValue(null, ATTR_START));
- final int end = Integer.parseInt(parser.getAttributeValue(null, ATTR_END));
- PageRange pageRange = new PageRange(start, end);
- if (pageRanges == null) {
- pageRanges = new ArrayList<PageRange>();
- }
- pageRanges.add(pageRange);
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_PAGE_RANGE);
- parser.next();
- }
- if (pageRanges != null) {
- PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
- pageRanges.toArray(pageRangesArray);
- printJob.setPages(pageRangesArray);
- }
-
- skipEmptyTextTags(parser);
- if (accept(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES)) {
-
- PrintAttributes.Builder builder = new PrintAttributes.Builder();
-
- String duplexMode = parser.getAttributeValue(null, ATTR_DUPLEX_MODE);
- builder.setDuplexMode(Integer.parseInt(duplexMode));
-
- String colorMode = parser.getAttributeValue(null, ATTR_COLOR_MODE);
- builder.setColorMode(Integer.parseInt(colorMode));
-
- String fittingMode = parser.getAttributeValue(null, ATTR_FITTING_MODE);
- builder.setFittingMode(Integer.parseInt(fittingMode));
-
- String orientation = parser.getAttributeValue(null, ATTR_ORIENTATION);
- builder.setOrientation(Integer.parseInt(orientation));
-
- parser.next();
-
- skipEmptyTextTags(parser);
- if (accept(parser, XmlPullParser.START_TAG, TAG_MEDIA_SIZE)) {
- String id = parser.getAttributeValue(null, ATTR_ID);
- label = parser.getAttributeValue(null, ATTR_LABEL);
- final int widthMils = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_WIDTH_MILS));
- final int heightMils = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_HEIGHT_MILS));
- MediaSize mediaSize = new MediaSize(id, label, widthMils, heightMils);
- builder.setMediaSize(mediaSize);
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_MEDIA_SIZE);
- parser.next();
- }
-
- skipEmptyTextTags(parser);
- if (accept(parser, XmlPullParser.START_TAG, TAG_RESOLUTION)) {
- String id = parser.getAttributeValue(null, ATTR_ID);
- label = parser.getAttributeValue(null, ATTR_LABEL);
- final int horizontalDpi = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_HORIZONTAL_DPI));
- final int verticalDpi = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_VERTICAL_DPI));
- Resolution resolution = new Resolution(id, label, horizontalDpi, verticalDpi);
- builder.setResolution(resolution);
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_RESOLUTION);
- parser.next();
- }
-
- skipEmptyTextTags(parser);
- if (accept(parser, XmlPullParser.START_TAG, TAG_MARGINS)) {
- final int leftMils = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_LEFT_MILS));
- final int topMils = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_TOP_MILS));
- final int rightMils = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_RIGHT_MILS));
- final int bottomMils = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_BOTTOM_MILS));
- Margins margins = new Margins(leftMils, topMils, rightMils, bottomMils);
- builder.setMargins(margins);
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_MARGINS);
- parser.next();
- }
-
- skipEmptyTextTags(parser);
- if (accept(parser, XmlPullParser.START_TAG, TAG_INPUT_TRAY)) {
- String id = parser.getAttributeValue(null, ATTR_ID);
- label = parser.getAttributeValue(null, ATTR_LABEL);
- Tray tray = new Tray(id, label);
- builder.setInputTray(tray);
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_INPUT_TRAY);
- parser.next();
- }
-
- skipEmptyTextTags(parser);
- if (accept(parser, XmlPullParser.START_TAG, TAG_OUTPUT_TRAY)) {
- String id = parser.getAttributeValue(null, ATTR_ID);
- label = parser.getAttributeValue(null, ATTR_LABEL);
- Tray tray = new Tray(id, label);
- builder.setOutputTray(tray);
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_OUTPUT_TRAY);
- parser.next();
- }
-
- printJob.setAttributes(builder.create());
-
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES);
- parser.next();
- }
-
- skipEmptyTextTags(parser);
- if (accept(parser, XmlPullParser.START_TAG, TAG_DOCUMENT_INFO)) {
- String name = parser.getAttributeValue(null, ATTR_NAME);
- final int pageCount = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_PAGE_COUNT));
- final int contentType = Integer.parseInt(parser.getAttributeValue(null,
- ATTR_CONTENT_TYPE));
- PrintDocumentInfo info = new PrintDocumentInfo.Builder(name)
- .setPageCount(pageCount)
- .setContentType(contentType).create();
- printJob.setDocumentInfo(info);
- parser.next();
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_DOCUMENT_INFO);
- parser.next();
- }
-
- mPrintJobs.add(printJob);
-
- if (DEBUG_PERSISTENCE) {
- Log.i(LOG_TAG, "[RESTORED] " + printJob);
- }
-
- skipEmptyTextTags(parser);
- expect(parser, XmlPullParser.END_TAG, TAG_JOB);
-
- return true;
- }
-
- private void expect(XmlPullParser parser, int type, String tag)
- throws IOException, XmlPullParserException {
- if (!accept(parser, type, tag)) {
- throw new XmlPullParserException("Exepected event: " + type
- + " and tag: " + tag + " but got event: " + parser.getEventType()
- + " and tag:" + parser.getName());
- }
- }
-
- private void skipEmptyTextTags(XmlPullParser parser)
- throws IOException, XmlPullParserException {
- while (accept(parser, XmlPullParser.TEXT, null)
- && "\n".equals(parser.getText())) {
- parser.next();
- }
- }
-
- private boolean accept(XmlPullParser parser, int type, String tag)
- throws IOException, XmlPullParserException {
- if (parser.getEventType() != type) {
- return false;
- }
- if (tag != null) {
- if (!tag.equals(parser.getName())) {
- return false;
- }
- } else if (parser.getName() != null) {
- return false;
- }
- return true;
- }
- }
-}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
index 4fab4f8..fda64c9 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java
@@ -21,9 +21,8 @@
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentSender;
-import android.os.Handler;
+import android.os.AsyncTask;
import android.os.IBinder;
-import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -32,14 +31,38 @@
import android.print.IPrintSpooler;
import android.print.IPrintSpoolerCallbacks;
import android.print.IPrintSpoolerClient;
-import android.print.IPrinterDiscoverySessionObserver;
+import android.print.PageRange;
import android.print.PrintAttributes;
+import android.print.PrintAttributes.Margins;
+import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Resolution;
+import android.print.PrintAttributes.Tray;
+import android.print.PrintDocumentInfo;
import android.print.PrintJobInfo;
+import android.print.PrintManager;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+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;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -48,22 +71,65 @@
*/
public final class PrintSpoolerService extends Service {
+ private static final String LOG_TAG = "PrintSpoolerService";
+
+ private static final boolean DEBUG_PRINT_JOB_LIFECYCLE = true;
+
+ private static final boolean DEBUG_PERSISTENCE = true;
+
+ private static final boolean PERSISTNECE_MANAGER_ENABLED = true;
+
private static final long CHECK_ALL_PRINTJOBS_HANDLED_DELAY = 5000;
- private static final String LOG_TAG = "PrintSpoolerService";
+ private static final String PRINT_FILE_EXTENSION = "pdf";
+
+ private static final Object sLock = new Object();
+
+ private final Object mLock = new Object();
+
+ private final List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
+
+ private static PrintSpoolerService sInstance;
+
+ private static int sPrintJobIdCounter;
private Intent mStartPrintJobConfigActivityIntent;
private IPrintSpoolerClient mClient;
- private Handler mHandler;
+ private HandlerCaller mHandlerCaller;
+
+ private PersistenceManager mPersistanceManager;
+
+ private NotificationController mNotificationController;
+
+ private PrinterDiscoverySession mDiscoverySession;
+
+ public static PrintSpoolerService peekInstance() {
+ synchronized (sLock) {
+ return sInstance;
+ }
+ }
@Override
public void onCreate() {
super.onCreate();
mStartPrintJobConfigActivityIntent = new Intent(PrintSpoolerService.this,
PrintJobConfigActivity.class);
- mHandler = new MyHandler(getMainLooper());
+ mHandlerCaller = new HandlerCaller(this, getMainLooper(),
+ new HandlerCallerCallback(), false);
+
+ mPersistanceManager = new PersistenceManager();
+ mNotificationController = new NotificationController(PrintSpoolerService.this);
+
+ synchronized (mLock) {
+ mPersistanceManager.readStateLocked();
+ handleReadPrintJobsLocked();
+ }
+
+ synchronized (sLock) {
+ sInstance = this;
+ }
}
@Override
@@ -72,10 +138,10 @@
@Override
public void getPrintJobInfos(IPrintSpoolerCallbacks callback,
ComponentName componentName, int state, int appId, int sequence)
- throws RemoteException {
+ throws RemoteException {
List<PrintJobInfo> printJobs = null;
try {
- printJobs = PrintSpooler.peekInstance().getPrintJobInfos(
+ printJobs = PrintSpoolerService.this.getPrintJobInfos(
componentName, state, appId);
} finally {
callback.onGetPrintJobInfosResult(printJobs, sequence);
@@ -87,7 +153,7 @@
int appId, int sequence) throws RemoteException {
PrintJobInfo printJob = null;
try {
- printJob = PrintSpooler.peekInstance().getPrintJobInfo(printJobId, appId);
+ printJob = PrintSpoolerService.this.getPrintJobInfo(printJobId, appId);
} finally {
callback.onGetPrintJobInfoResult(printJob, sequence);
}
@@ -98,11 +164,11 @@
public void createPrintJob(String printJobName, IPrintClient client,
IPrintDocumentAdapter printAdapter, PrintAttributes attributes,
IPrintSpoolerCallbacks callback, int appId, int sequence)
- throws RemoteException {
+ throws RemoteException {
PrintJobInfo printJob = null;
try {
- printJob = PrintSpooler.peekInstance().createPrintJob(printJobName, client,
- attributes, appId);
+ printJob = PrintSpoolerService.this.createPrintJob(
+ printJobName, client, attributes, appId);
if (printJob != null) {
Intent intent = mStartPrintJobConfigActivityIntent;
intent.putExtra(PrintJobConfigActivity.EXTRA_PRINT_DOCUMENT_ADAPTER,
@@ -113,13 +179,12 @@
IntentSender sender = PendingIntent.getActivity(
PrintSpoolerService.this, 0, intent, PendingIntent.FLAG_ONE_SHOT
- | PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
+ | PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = client;
- args.arg2 = sender;
- mHandler.obtainMessage(MyHandler.MSG_START_PRINT_JOB_CONFIG_ACTIVITY,
- args).sendToTarget();
+ Message message = mHandlerCaller.obtainMessageOO(
+ HandlerCallerCallback.MSG_START_PRINT_JOB_CONFIG_ACTIVITY,
+ client, sender);
+ mHandlerCaller.executeOrSendMessage(message);
}
} finally {
callback.onCreatePrintJobResult(printJob, sequence);
@@ -127,11 +192,11 @@
}
@Override
- public void setPrintJobState(int printJobId, int state, CharSequence error,
+ public void setPrintJobState(int printJobId, int state, String error,
IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
boolean success = false;
try {
- success = PrintSpooler.peekInstance().setPrintJobState(
+ success = PrintSpoolerService.this.setPrintJobState(
printJobId, state, error);
} finally {
callback.onSetPrintJobStateResult(success, sequece);
@@ -143,7 +208,7 @@
IPrintSpoolerCallbacks callback, int sequece) throws RemoteException {
boolean success = false;
try {
- success = PrintSpooler.peekInstance().setPrintJobTag(printJobId, tag);
+ success = PrintSpoolerService.this.setPrintJobTag(printJobId, tag);
} finally {
callback.onSetPrintJobTagResult(success, sequece);
}
@@ -151,60 +216,191 @@
@Override
public void writePrintJobData(ParcelFileDescriptor fd, int printJobId) {
- PrintSpooler.peekInstance().writePrintJobData(fd, printJobId);
+ PrintSpoolerService.this.writePrintJobData(fd, printJobId);
}
@Override
public void setClient(IPrintSpoolerClient client) {
- mHandler.obtainMessage(MyHandler.MSG_SET_CLIENT, client).sendToTarget();
+ Message message = mHandlerCaller.obtainMessageO(
+ HandlerCallerCallback.MSG_SET_CLIENT, client);
+ mHandlerCaller.executeOrSendMessage(message);
+ }
+
+ @Override
+ public void onPrintersAdded(List<PrinterInfo> printers) {
+ Message message = mHandlerCaller.obtainMessageO(
+ HandlerCallerCallback.MSG_ON_PRINTERS_ADDED, printers);
+ mHandlerCaller.executeOrSendMessage(message);
+ }
+
+ @Override
+ public void onPrintersRemoved(List<PrinterId> printerIds) {
+ Message message = mHandlerCaller.obtainMessageO(
+ HandlerCallerCallback.MSG_ON_PRINTERS_REMOVED, printerIds);
+ mHandlerCaller.executeOrSendMessage(message);
+ }
+
+ @Override
+ public void onPrintersUpdated(List<PrinterInfo> printers) {
+ Message message = mHandlerCaller.obtainMessageO(
+ HandlerCallerCallback.MSG_ON_PRINTERS_UPDATED, printers);
+ mHandlerCaller.executeOrSendMessage(message);
}
};
}
- public void onPrintJobQueued(PrintJobInfo printJob) {
- mHandler.obtainMessage(MyHandler.MSG_ON_PRINT_JOB_QUEUED,
- printJob).sendToTarget();
+ public void createPrinterDiscoverySession() {
+ Message message = mHandlerCaller.obtainMessage(
+ HandlerCallerCallback.MSG_CREATE_PRINTER_DISCOVERY_SESSION);
+ mHandlerCaller.executeOrSendMessage(message);
}
- public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
- mHandler.obtainMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION,
- observer).sendToTarget();
+ public void destroyPrinterDiscoverySession() {
+ Message message = mHandlerCaller.obtainMessage(
+ HandlerCallerCallback.MSG_DESTROY_PRINTER_DISCOVERY_SESSION);
+ mHandlerCaller.executeOrSendMessage(message);
}
- public void onAllPrintJobsForServiceHandled(ComponentName service) {
- mHandler.obtainMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED,
- service).sendToTarget();
+ public void startPrinterDiscovery(List<PrinterId> priorityList) {
+ Message message = mHandlerCaller.obtainMessageO(
+ HandlerCallerCallback.MSG_START_PRINTER_DISCOVERY, priorityList);
+ mHandlerCaller.executeOrSendMessage(message);
}
- public void onAllPrintJobsHandled() {
- mHandler.sendEmptyMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_HANDLED);
+ public void stopPrinterDiscovery() {
+ Message message = mHandlerCaller.obtainMessage(
+ HandlerCallerCallback.MSG_STOP_PRINTER_DISCOVERY);
+ mHandlerCaller.executeOrSendMessage(message);
}
- private final class MyHandler extends Handler {
- public static final int MSG_SET_CLIENT = 1;
- public static final int MSG_START_PRINT_JOB_CONFIG_ACTIVITY = 2;
- public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 3;
- public static final int MSG_ON_PRINT_JOB_QUEUED = 5;
- public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 6;
- public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 7;
- public static final int MSG_CHECK_ALL_PRINTJOBS_HANDLED = 9;
+ public void requestPrinterUpdate(PrinterId pritnerId) {
+ Message message = mHandlerCaller.obtainMessageO(
+ HandlerCallerCallback.MSG_REQUEST_PRINTER_UPDATE, pritnerId);
+ mHandlerCaller.executeOrSendMessage(message);
+ }
- public MyHandler(Looper looper) {
- super(looper, null, false);
- }
+
+ private void sendOnPrintJobQueued(PrintJobInfo printJob) {
+ Message message = mHandlerCaller.obtainMessageO(
+ HandlerCallerCallback.MSG_ON_PRINT_JOB_QUEUED, printJob);
+ mHandlerCaller.executeOrSendMessage(message);
+ }
+
+ private void sendOnAllPrintJobsForServiceHandled(ComponentName service) {
+ Message message = mHandlerCaller.obtainMessageO(
+ HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED, service);
+ mHandlerCaller.executeOrSendMessage(message);
+ }
+
+ private void sendOnAllPrintJobsHandled() {
+ Message message = mHandlerCaller.obtainMessage(
+ HandlerCallerCallback.MSG_ON_ALL_PRINT_JOBS_HANDLED);
+ mHandlerCaller.executeOrSendMessage(message);
+ }
+
+ private final class HandlerCallerCallback implements HandlerCaller.Callback {
+ public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 1;
+ public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2;
+ public static final int MSG_START_PRINTER_DISCOVERY = 3;
+ public static final int MSG_STOP_PRINTER_DISCOVERY = 4;
+ public static final int MSG_REQUEST_PRINTER_UPDATE = 5;
+
+ public static final int MSG_ON_PRINTERS_ADDED = 6;
+ public static final int MSG_ON_PRINTERS_REMOVED = 7;
+ public static final int MSG_ON_PRINTERS_UPDATED = 8;
+
+ public static final int MSG_SET_CLIENT = 9;
+ public static final int MSG_START_PRINT_JOB_CONFIG_ACTIVITY = 10;
+ public static final int MSG_ON_PRINT_JOB_QUEUED = 11;
+ public static final int MSG_ON_ALL_PRINT_JOBS_FOR_SERIVICE_HANDLED = 12;
+ public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 13;
+ public static final int MSG_CHECK_ALL_PRINTJOBS_HANDLED = 14;
@Override
- public void handleMessage(Message message) {
+ @SuppressWarnings("unchecked")
+ public void executeMessage(Message message) {
switch (message.what) {
+ case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
+ final IPrintSpoolerClient client;
+ synchronized (mLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ client.createPrinterDiscoverySession();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error creating printer discovery session.", re);
+ }
+ }
+ } break;
+
+ case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: {
+ final IPrintSpoolerClient client;
+ synchronized (mLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ client.destroyPrinterDiscoverySession();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error destroying printer discovery session.", re);
+ }
+ }
+ } break;
+
+ case MSG_START_PRINTER_DISCOVERY: {
+ final IPrintSpoolerClient client;
+ synchronized (mLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ List<PrinterId> priorityList = (ArrayList<PrinterId>) message.obj;
+ try {
+ client.startPrinterDiscovery(priorityList);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error starting printer discovery.", re);
+ }
+ }
+ } break;
+
+ case MSG_STOP_PRINTER_DISCOVERY: {
+ final IPrintSpoolerClient client;
+ synchronized (mLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ client.stopPrinterDiscovery();
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error stopping printer discovery.", re);
+ }
+ }
+ } break;
+
+ case MSG_REQUEST_PRINTER_UPDATE: {
+ final IPrintSpoolerClient client;
+ synchronized (mLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ PrinterId printerId = (PrinterId) message.obj;
+ try {
+ client.requestPrinterUpdate(printerId);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error requesing printer update.", re);
+ }
+ }
+ } break;
+
case MSG_SET_CLIENT: {
- mClient = (IPrintSpoolerClient) message.obj;
- if (mClient != null) {
- PrintSpooler.createInstance(PrintSpoolerService.this);
- mHandler.sendEmptyMessageDelayed(
- MyHandler.MSG_CHECK_ALL_PRINTJOBS_HANDLED,
- CHECK_ALL_PRINTJOBS_HANDLED_DELAY);
- } else {
- PrintSpooler.destroyInstance();
+ synchronized (mLock) {
+ mClient = (IPrintSpoolerClient) message.obj;
+ if (mClient != null) {
+ Message msg = mHandlerCaller.obtainMessage(
+ HandlerCallerCallback.MSG_CHECK_ALL_PRINTJOBS_HANDLED);
+ mHandlerCaller.sendMessageDelayed(msg,
+ CHECK_ALL_PRINTJOBS_HANDLED_DELAY);
+ }
}
} break;
@@ -220,18 +416,6 @@
}
} break;
- case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
- IPrinterDiscoverySessionObserver observer =
- (IPrinterDiscoverySessionObserver) message.obj;
- if (mClient != null) {
- try {
- mClient.createPrinterDiscoverySession(observer);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error creating printer discovery session.", re);
- }
- }
- } break;
-
case MSG_ON_PRINT_JOB_QUEUED: {
PrintJobInfo printJob = (PrintJobInfo) message.obj;
if (mClient != null) {
@@ -266,12 +450,948 @@
} break;
case MSG_CHECK_ALL_PRINTJOBS_HANDLED: {
- PrintSpooler spooler = PrintSpooler.peekInstance();
- if (spooler != null) {
- spooler.checkAllPrintJobsHandled();
+ checkAllPrintJobsHandled();
+ } break;
+
+ case MSG_ON_PRINTERS_ADDED: {
+ final PrinterDiscoverySession session;
+ synchronized (mLock) {
+ session = mDiscoverySession;
+ }
+ if (session != null) {
+ List<PrinterInfo> printers = (ArrayList<PrinterInfo>) message.obj;
+ session.onPrintersAdded(printers);
+ }
+ } break;
+
+ case MSG_ON_PRINTERS_REMOVED: {
+ final PrinterDiscoverySession session;
+ synchronized (mLock) {
+ session = mDiscoverySession;
+ }
+ if (session != null) {
+ List<PrinterId> printerIds = (ArrayList<PrinterId>) message.obj;
+ session.onPrintersRemoved(printerIds);
+ }
+ } break;
+
+ case MSG_ON_PRINTERS_UPDATED: {
+ final PrinterDiscoverySession session;
+ synchronized (mLock) {
+ session = mDiscoverySession;
+ }
+ if (session != null) {
+ List<PrinterInfo> printers = (ArrayList<PrinterInfo>) message.obj;
+ session.onPrintersUpdated(printers);
}
} break;
}
}
}
+
+ public List<PrintJobInfo> getPrintJobInfos(ComponentName componentName,
+ int state, int appId) {
+ List<PrintJobInfo> foundPrintJobs = null;
+ synchronized (mLock) {
+ final int printJobCount = mPrintJobs.size();
+ for (int i = 0; i < printJobCount; i++) {
+ PrintJobInfo printJob = mPrintJobs.get(i);
+ PrinterId printerId = printJob.getPrinterId();
+ final boolean sameComponent = (componentName == null
+ || (printerId != null
+ && componentName.equals(printerId.getServiceName())));
+ final boolean sameAppId = appId == PrintManager.APP_ID_ANY
+ || printJob.getAppId() == appId;
+ final boolean sameState = (state == printJob.getState())
+ || (state == PrintJobInfo.STATE_ANY)
+ || (state == PrintJobInfo.STATE_ANY_VISIBLE_TO_CLIENTS
+ && printJob.getState() > PrintJobInfo.STATE_CREATED);
+ if (sameComponent && sameAppId && sameState) {
+ if (foundPrintJobs == null) {
+ foundPrintJobs = new ArrayList<PrintJobInfo>();
+ }
+ foundPrintJobs.add(printJob);
+ }
+ }
+ }
+ return foundPrintJobs;
+ }
+
+ public PrintJobInfo getPrintJobInfo(int printJobId, int appId) {
+ synchronized (mLock) {
+ final int printJobCount = mPrintJobs.size();
+ for (int i = 0; i < printJobCount; i++) {
+ PrintJobInfo printJob = mPrintJobs.get(i);
+ if (printJob.getId() == printJobId
+ && (appId == PrintManager.APP_ID_ANY
+ || appId == printJob.getAppId())) {
+ return printJob;
+ }
+ }
+ return null;
+ }
+ }
+
+ public PrintJobInfo createPrintJob(String label, IPrintClient client,
+ PrintAttributes attributes, int appId) {
+ synchronized (mLock) {
+ final int printJobId = generatePrintJobIdLocked();
+ PrintJobInfo printJob = new PrintJobInfo();
+ printJob.setId(printJobId);
+ printJob.setAppId(appId);
+ printJob.setLabel(label);
+ printJob.setAttributes(attributes);
+ printJob.setState(PrintJobInfo.STATE_CREATED);
+
+ addPrintJobLocked(printJob);
+
+ return printJob;
+ }
+ }
+
+ private void handleReadPrintJobsLocked() {
+ final int printJobCount = mPrintJobs.size();
+ for (int i = 0; i < printJobCount; i++) {
+ PrintJobInfo printJob = mPrintJobs.get(i);
+
+ // Update the notification.
+ mNotificationController.onPrintJobStateChanged(printJob);
+
+ switch (printJob.getState()) {
+ case PrintJobInfo.STATE_QUEUED:
+ case PrintJobInfo.STATE_STARTED: {
+ // We have a print job that was queued or started in the
+ // past
+ // but the device battery died or a crash occurred. In this
+ // case
+ // we assume the print job failed and let the user decide
+ // whether
+ // to restart the job or just
+ setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED,
+ getString(R.string.no_connection_to_printer));
+ }
+ break;
+ }
+ }
+ }
+
+ public void checkAllPrintJobsHandled() {
+ synchronized (mLock) {
+ if (!hasActivePrintJobsLocked()) {
+ notifyOnAllPrintJobsHandled();
+ }
+ }
+ }
+
+ private void setPrinterDiscoverySessionClient(PrinterDiscoverySession session) {
+ synchronized (mLock) {
+ mDiscoverySession = session;
+ }
+ }
+
+ private int generatePrintJobIdLocked() {
+ int printJobId = sPrintJobIdCounter++;
+ while (isDuplicatePrintJobId(printJobId)) {
+ printJobId = sPrintJobIdCounter++;
+ }
+ return printJobId;
+ }
+
+ private boolean isDuplicatePrintJobId(int printJobId) {
+ final int printJobCount = mPrintJobs.size();
+ for (int j = 0; j < printJobCount; j++) {
+ PrintJobInfo printJob = mPrintJobs.get(j);
+ if (printJob.getId() == printJobId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void writePrintJobData(final ParcelFileDescriptor fd, final int printJobId) {
+ final PrintJobInfo printJob;
+ synchronized (mLock) {
+ printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ }
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ FileInputStream in = null;
+ FileOutputStream out = null;
+ try {
+ if (printJob != null) {
+ File file = generateFileForPrintJob(printJobId);
+ in = new FileInputStream(file);
+ out = new FileOutputStream(fd.getFileDescriptor());
+ }
+ final byte[] buffer = new byte[8192];
+ while (true) {
+ final int readByteCount = in.read(buffer);
+ if (readByteCount < 0) {
+ return null;
+ }
+ out.write(buffer, 0, readByteCount);
+ }
+ } catch (FileNotFoundException fnfe) {
+ Log.e(LOG_TAG, "Error writing print job data!", fnfe);
+ } catch (IOException ioe) {
+ Log.e(LOG_TAG, "Error writing print job data!", ioe);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ IoUtils.closeQuietly(fd);
+ }
+ Log.i(LOG_TAG, "[END WRITE]");
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ }
+
+ public File generateFileForPrintJob(int printJobId) {
+ return new File(getFilesDir(), "print_job_"
+ + printJobId + "." + PRINT_FILE_EXTENSION);
+ }
+
+ private void addPrintJobLocked(PrintJobInfo printJob) {
+ mPrintJobs.add(printJob);
+ if (DEBUG_PRINT_JOB_LIFECYCLE) {
+ Slog.i(LOG_TAG, "[ADD] " + printJob);
+ }
+ }
+
+ private void removePrintJobLocked(PrintJobInfo printJob) {
+ if (mPrintJobs.remove(printJob)) {
+ generateFileForPrintJob(printJob.getId()).delete();
+ if (DEBUG_PRINT_JOB_LIFECYCLE) {
+ Slog.i(LOG_TAG, "[REMOVE] " + printJob);
+ }
+ }
+ }
+
+ public boolean setPrintJobState(int printJobId, int state, String error) {
+ boolean success = false;
+
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ success = true;
+
+ printJob.setState(state);
+ printJob.setFailureReason(error);
+ mNotificationController.onPrintJobStateChanged(printJob);
+
+ if (DEBUG_PRINT_JOB_LIFECYCLE) {
+ Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
+ }
+
+ switch (state) {
+ case PrintJobInfo.STATE_COMPLETED:
+ case PrintJobInfo.STATE_CANCELED:
+ removePrintJobLocked(printJob);
+ // $fall-through$
+
+ case PrintJobInfo.STATE_FAILED: {
+ PrinterId printerId = printJob.getPrinterId();
+ if (printerId != null) {
+ ComponentName service = printerId.getServiceName();
+ if (!hasActivePrintJobsForServiceLocked(service)) {
+ sendOnAllPrintJobsForServiceHandled(service);
+ }
+ }
+ } break;
+
+ case PrintJobInfo.STATE_QUEUED: {
+ sendOnPrintJobQueued(new PrintJobInfo(printJob));
+ } break;
+ }
+
+ if (shouldPersistPrintJob(printJob)) {
+ mPersistanceManager.writeStateLocked();
+ }
+
+ if (!hasActivePrintJobsLocked()) {
+ notifyOnAllPrintJobsHandled();
+ }
+ }
+ }
+
+ return success;
+ }
+
+ public boolean hasActivePrintJobsLocked() {
+ final int printJobCount = mPrintJobs.size();
+ for (int i = 0; i < printJobCount; i++) {
+ PrintJobInfo printJob = mPrintJobs.get(i);
+ if (isActiveState(printJob.getState())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean hasActivePrintJobsForServiceLocked(ComponentName service) {
+ final int printJobCount = mPrintJobs.size();
+ for (int i = 0; i < printJobCount; i++) {
+ PrintJobInfo printJob = mPrintJobs.get(i);
+ if (isActiveState(printJob.getState())
+ && printJob.getPrinterId().getServiceName().equals(service)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isActiveState(int printJobState) {
+ return printJobState == PrintJobInfo.STATE_CREATED
+ || printJobState == PrintJobInfo.STATE_QUEUED
+ || printJobState == PrintJobInfo.STATE_STARTED;
+ }
+
+ public boolean setPrintJobTag(int printJobId, String tag) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ String printJobTag = printJob.getTag();
+ if (printJobTag == null) {
+ if (tag == null) {
+ return false;
+ }
+ } else if (printJobTag.equals(tag)) {
+ return false;
+ }
+ printJob.setTag(tag);
+ if (shouldPersistPrintJob(printJob)) {
+ mPersistanceManager.writeStateLocked();
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void setPrintJobCopiesNoPersistence(int printJobId, int copies) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setCopies(copies);
+ }
+ }
+ }
+
+ public void setPrintJobPrintDocumentInfoNoPersistence(int printJobId, PrintDocumentInfo info) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setDocumentInfo(info);
+ }
+ }
+ }
+
+ public void setPrintJobAttributesNoPersistence(int printJobId, PrintAttributes attributes) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setAttributes(attributes);
+ }
+ }
+ }
+
+ public void setPrintJobPrinterNoPersistence(int printJobId, PrinterInfo printer) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setPrinterId(printer.getId());
+ printJob.setPrinterName(printer.getName());
+ }
+ }
+ }
+
+ public void setPrintJobPagesNoPersistence(int printJobId, PageRange[] pages) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setPages(pages);
+ }
+ }
+ }
+
+ private boolean shouldPersistPrintJob(PrintJobInfo printJob) {
+ return printJob.getState() >= PrintJobInfo.STATE_QUEUED;
+ }
+
+ private void notifyOnAllPrintJobsHandled() {
+ // This has to run on the tread that is persisting the current state
+ // since this call may result in the system unbinding from the spooler
+ // and as a result the spooler process may get killed before the write
+ // completes.
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ sendOnAllPrintJobsHandled();
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+
+ private final class PersistenceManager {
+ private static final String PERSIST_FILE_NAME = "print_spooler_state.xml";
+
+ private static final String TAG_SPOOLER = "spooler";
+ private static final String TAG_JOB = "job";
+
+ private static final String TAG_PRINTER_ID = "printerId";
+ private static final String TAG_PAGE_RANGE = "pageRange";
+ private static final String TAG_ATTRIBUTES = "attributes";
+ private static final String TAG_DOCUMENT_INFO = "documentInfo";
+
+ private static final String ATTR_ID = "id";
+ private static final String ATTR_LABEL = "label";
+ private static final String ATTR_STATE = "state";
+ private static final String ATTR_APP_ID = "appId";
+ private static final String ATTR_USER_ID = "userId";
+ private static final String ATTR_TAG = "tag";
+ private static final String ATTR_COPIES = "copies";
+
+ private static final String TAG_MEDIA_SIZE = "mediaSize";
+ private static final String TAG_RESOLUTION = "resolution";
+ private static final String TAG_MARGINS = "margins";
+ private static final String TAG_INPUT_TRAY = "inputTray";
+ private static final String TAG_OUTPUT_TRAY = "outputTray";
+
+ private static final String ATTR_DUPLEX_MODE = "duplexMode";
+ private static final String ATTR_COLOR_MODE = "colorMode";
+ private static final String ATTR_FITTING_MODE = "fittingMode";
+ private static final String ATTR_ORIENTATION = "orientation";
+
+ private static final String ATTR_LOCAL_ID = "printerName";
+ private static final String ATTR_SERVICE_NAME = "serviceName";
+
+ private static final String ATTR_WIDTH_MILS = "widthMils";
+ private static final String ATTR_HEIGHT_MILS = "heightMils";
+
+ private static final String ATTR_HORIZONTAL_DPI = "horizontalDip";
+ private static final String ATTR_VERTICAL_DPI = "verticalDpi";
+
+ private static final String ATTR_LEFT_MILS = "leftMils";
+ private static final String ATTR_TOP_MILS = "topMils";
+ private static final String ATTR_RIGHT_MILS = "rightMils";
+ private static final String ATTR_BOTTOM_MILS = "bottomMils";
+
+ private static final String ATTR_START = "start";
+ private static final String ATTR_END = "end";
+
+ private static final String ATTR_NAME = "name";
+ private static final String ATTR_PAGE_COUNT = "pageCount";
+ private static final String ATTR_CONTENT_TYPE = "contentType";
+
+ private final AtomicFile mStatePersistFile;
+
+ private boolean mWriteStateScheduled;
+
+ private PersistenceManager() {
+ mStatePersistFile = new AtomicFile(new File(getFilesDir(),
+ PERSIST_FILE_NAME));
+ }
+
+ public void writeStateLocked() {
+ if (!PERSISTNECE_MANAGER_ENABLED) {
+ return;
+ }
+ if (mWriteStateScheduled) {
+ return;
+ }
+ mWriteStateScheduled = true;
+ new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected Void doInBackground(Void... params) {
+ synchronized (mLock) {
+ mWriteStateScheduled = false;
+ doWriteStateLocked();
+ }
+ return null;
+ }
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }
+
+ private void doWriteStateLocked() {
+ if (DEBUG_PERSISTENCE) {
+ Log.i(LOG_TAG, "[PERSIST START]");
+ }
+ FileOutputStream out = null;
+ try {
+ out = mStatePersistFile.startWrite();
+
+ XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(out, "utf-8");
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_SPOOLER);
+
+ List<PrintJobInfo> printJobs = mPrintJobs;
+
+ final int printJobCount = printJobs.size();
+ for (int j = 0; j < printJobCount; j++) {
+ PrintJobInfo printJob = printJobs.get(j);
+
+ final int state = printJob.getState();
+ if (state < PrintJobInfo.STATE_QUEUED
+ || state > PrintJobInfo.STATE_CANCELED) {
+ continue;
+ }
+
+ serializer.startTag(null, TAG_JOB);
+
+ serializer.attribute(null, ATTR_ID, String.valueOf(printJob.getId()));
+ serializer.attribute(null, ATTR_LABEL, printJob.getLabel().toString());
+ serializer.attribute(null, ATTR_STATE, String.valueOf(printJob.getState()));
+ serializer.attribute(null, ATTR_APP_ID, String.valueOf(printJob.getAppId()));
+ serializer.attribute(null, ATTR_USER_ID, String.valueOf(printJob.getUserId()));
+ String tag = printJob.getTag();
+ if (tag != null) {
+ serializer.attribute(null, ATTR_TAG, tag);
+ }
+ serializer.attribute(null, ATTR_COPIES, String.valueOf(printJob.getCopies()));
+
+ PrinterId printerId = printJob.getPrinterId();
+ if (printerId != null) {
+ serializer.startTag(null, TAG_PRINTER_ID);
+ serializer.attribute(null, ATTR_LOCAL_ID, printerId.getLocalId());
+ serializer.attribute(null, ATTR_SERVICE_NAME, printerId.getServiceName()
+ .flattenToString());
+ serializer.endTag(null, TAG_PRINTER_ID);
+ }
+
+ PageRange[] pages = printJob.getPages();
+ if (pages != null) {
+ for (int i = 0; i < pages.length; i++) {
+ serializer.startTag(null, TAG_PAGE_RANGE);
+ serializer.attribute(null, ATTR_START, String.valueOf(
+ pages[i].getStart()));
+ serializer.attribute(null, ATTR_END, String.valueOf(
+ pages[i].getEnd()));
+ serializer.endTag(null, TAG_PAGE_RANGE);
+ }
+ }
+
+ PrintAttributes attributes = printJob.getAttributes();
+ if (attributes != null) {
+ serializer.startTag(null, TAG_ATTRIBUTES);
+
+ final int duplexMode = attributes.getDuplexMode();
+ serializer.attribute(null, ATTR_DUPLEX_MODE,
+ String.valueOf(duplexMode));
+
+ final int colorMode = attributes.getColorMode();
+ serializer.attribute(null, ATTR_COLOR_MODE,
+ String.valueOf(colorMode));
+
+ final int fittingMode = attributes.getFittingMode();
+ serializer.attribute(null, ATTR_FITTING_MODE,
+ String.valueOf(fittingMode));
+
+ final int orientation = attributes.getOrientation();
+ serializer.attribute(null, ATTR_ORIENTATION,
+ String.valueOf(orientation));
+
+ MediaSize mediaSize = attributes.getMediaSize();
+ if (mediaSize != null) {
+ serializer.startTag(null, TAG_MEDIA_SIZE);
+ serializer.attribute(null, ATTR_ID, mediaSize.getId());
+ serializer.attribute(null, ATTR_LABEL, mediaSize.getLabel()
+ .toString());
+ serializer.attribute(null, ATTR_WIDTH_MILS, String.valueOf(
+ mediaSize.getWidthMils()));
+ serializer.attribute(null, ATTR_HEIGHT_MILS, String.valueOf(
+ mediaSize.getHeightMils()));
+ serializer.endTag(null, TAG_MEDIA_SIZE);
+ }
+
+ Resolution resolution = attributes.getResolution();
+ if (resolution != null) {
+ serializer.startTag(null, TAG_RESOLUTION);
+ serializer.attribute(null, ATTR_ID, resolution.getId());
+ serializer.attribute(null, ATTR_LABEL, resolution.getLabel()
+ .toString());
+ serializer.attribute(null, ATTR_HORIZONTAL_DPI, String.valueOf(
+ resolution.getHorizontalDpi()));
+ serializer.attribute(null, ATTR_VERTICAL_DPI, String.valueOf(
+ resolution.getVerticalDpi()));
+ serializer.endTag(null, TAG_RESOLUTION);
+ }
+
+ Margins margins = attributes.getMargins();
+ if (margins != null) {
+ serializer.startTag(null, TAG_MARGINS);
+ serializer.attribute(null, ATTR_LEFT_MILS, String.valueOf(
+ margins.getLeftMils()));
+ serializer.attribute(null, ATTR_TOP_MILS, String.valueOf(
+ margins.getTopMils()));
+ serializer.attribute(null, ATTR_RIGHT_MILS, String.valueOf(
+ margins.getRightMils()));
+ serializer.attribute(null, ATTR_BOTTOM_MILS, String.valueOf(
+ margins.getBottomMils()));
+ serializer.endTag(null, TAG_MARGINS);
+ }
+
+ Tray inputTray = attributes.getInputTray();
+ if (inputTray != null) {
+ serializer.startTag(null, TAG_INPUT_TRAY);
+ serializer.attribute(null, ATTR_ID, inputTray.getId());
+ serializer.attribute(null, ATTR_LABEL, inputTray.getLabel()
+ .toString());
+ serializer.endTag(null, TAG_INPUT_TRAY);
+ }
+
+ Tray outputTray = attributes.getOutputTray();
+ if (outputTray != null) {
+ serializer.startTag(null, TAG_OUTPUT_TRAY);
+ serializer.attribute(null, ATTR_ID, outputTray.getId());
+ serializer.attribute(null, ATTR_LABEL, outputTray.getLabel()
+ .toString());
+ serializer.endTag(null, TAG_OUTPUT_TRAY);
+ }
+
+ serializer.endTag(null, TAG_ATTRIBUTES);
+ }
+
+ PrintDocumentInfo documentInfo = printJob.getDocumentInfo();
+ if (documentInfo != null) {
+ serializer.startTag(null, TAG_DOCUMENT_INFO);
+ serializer.attribute(null, ATTR_NAME, documentInfo.getName());
+ serializer.attribute(null, ATTR_CONTENT_TYPE, String.valueOf(
+ documentInfo.getContentType()));
+ serializer.attribute(null, ATTR_PAGE_COUNT, String.valueOf(
+ documentInfo.getPageCount()));
+ serializer.endTag(null, TAG_DOCUMENT_INFO);
+ }
+
+ serializer.endTag(null, TAG_JOB);
+
+ if (DEBUG_PERSISTENCE) {
+ Log.i(LOG_TAG, "[PERSISTED] " + printJob);
+ }
+ }
+
+ serializer.endTag(null, TAG_SPOOLER);
+ serializer.endDocument();
+ mStatePersistFile.finishWrite(out);
+ if (DEBUG_PERSISTENCE) {
+ Log.i(LOG_TAG, "[PERSIST END]");
+ }
+ } catch (IOException e) {
+ Slog.w(LOG_TAG, "Failed to write state, restoring backup.", e);
+ mStatePersistFile.failWrite(out);
+ } finally {
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ public void readStateLocked() {
+ if (!PERSISTNECE_MANAGER_ENABLED) {
+ return;
+ }
+ FileInputStream in = null;
+ try {
+ in = mStatePersistFile.openRead();
+ } catch (FileNotFoundException e) {
+ Log.i(LOG_TAG, "No existing print spooler state.");
+ return;
+ }
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+ parseState(parser);
+ } catch (IllegalStateException ise) {
+ Slog.w(LOG_TAG, "Failed parsing ", ise);
+ } catch (NullPointerException npe) {
+ Slog.w(LOG_TAG, "Failed parsing ", npe);
+ } catch (NumberFormatException nfe) {
+ Slog.w(LOG_TAG, "Failed parsing ", nfe);
+ } catch (XmlPullParserException xppe) {
+ Slog.w(LOG_TAG, "Failed parsing ", xppe);
+ } catch (IOException ioe) {
+ Slog.w(LOG_TAG, "Failed parsing ", ioe);
+ } catch (IndexOutOfBoundsException iobe) {
+ Slog.w(LOG_TAG, "Failed parsing ", iobe);
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
+ private void parseState(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.START_TAG, TAG_SPOOLER);
+ parser.next();
+
+ while (parsePrintJob(parser)) {
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_SPOOLER);
+ }
+
+ private boolean parsePrintJob(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ skipEmptyTextTags(parser);
+ if (!accept(parser, XmlPullParser.START_TAG, TAG_JOB)) {
+ return false;
+ }
+
+ PrintJobInfo printJob = new PrintJobInfo();
+
+ final int printJobId = Integer.parseInt(parser.getAttributeValue(null, ATTR_ID));
+ printJob.setId(printJobId);
+ String label = parser.getAttributeValue(null, ATTR_LABEL);
+ printJob.setLabel(label);
+ final int state = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATE));
+ printJob.setState(state);
+ final int appId = Integer.parseInt(parser.getAttributeValue(null, ATTR_APP_ID));
+ printJob.setAppId(appId);
+ final int userId = Integer.parseInt(parser.getAttributeValue(null, ATTR_USER_ID));
+ printJob.setUserId(userId);
+ String tag = parser.getAttributeValue(null, ATTR_TAG);
+ printJob.setTag(tag);
+ String copies = parser.getAttributeValue(null, ATTR_COPIES);
+ printJob.setCopies(Integer.parseInt(copies));
+
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ if (accept(parser, XmlPullParser.START_TAG, TAG_PRINTER_ID)) {
+ String localId = parser.getAttributeValue(null, ATTR_LOCAL_ID);
+ ComponentName service = ComponentName.unflattenFromString(parser.getAttributeValue(
+ null, ATTR_SERVICE_NAME));
+ printJob.setPrinterId(new PrinterId(service, localId));
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ List<PageRange> pageRanges = null;
+ while (accept(parser, XmlPullParser.START_TAG, TAG_PAGE_RANGE)) {
+ final int start = Integer.parseInt(parser.getAttributeValue(null, ATTR_START));
+ final int end = Integer.parseInt(parser.getAttributeValue(null, ATTR_END));
+ PageRange pageRange = new PageRange(start, end);
+ if (pageRanges == null) {
+ pageRanges = new ArrayList<PageRange>();
+ }
+ pageRanges.add(pageRange);
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_PAGE_RANGE);
+ parser.next();
+ }
+ if (pageRanges != null) {
+ PageRange[] pageRangesArray = new PageRange[pageRanges.size()];
+ pageRanges.toArray(pageRangesArray);
+ printJob.setPages(pageRangesArray);
+ }
+
+ skipEmptyTextTags(parser);
+ if (accept(parser, XmlPullParser.START_TAG, TAG_ATTRIBUTES)) {
+
+ PrintAttributes.Builder builder = new PrintAttributes.Builder();
+
+ String duplexMode = parser.getAttributeValue(null, ATTR_DUPLEX_MODE);
+ builder.setDuplexMode(Integer.parseInt(duplexMode));
+
+ String colorMode = parser.getAttributeValue(null, ATTR_COLOR_MODE);
+ builder.setColorMode(Integer.parseInt(colorMode));
+
+ String fittingMode = parser.getAttributeValue(null, ATTR_FITTING_MODE);
+ builder.setFittingMode(Integer.parseInt(fittingMode));
+
+ String orientation = parser.getAttributeValue(null, ATTR_ORIENTATION);
+ builder.setOrientation(Integer.parseInt(orientation));
+
+ parser.next();
+
+ skipEmptyTextTags(parser);
+ if (accept(parser, XmlPullParser.START_TAG, TAG_MEDIA_SIZE)) {
+ String id = parser.getAttributeValue(null, ATTR_ID);
+ label = parser.getAttributeValue(null, ATTR_LABEL);
+ final int widthMils = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_WIDTH_MILS));
+ final int heightMils = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_HEIGHT_MILS));
+ MediaSize mediaSize = new MediaSize(id, label, widthMils, heightMils);
+ builder.setMediaSize(mediaSize);
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_MEDIA_SIZE);
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ if (accept(parser, XmlPullParser.START_TAG, TAG_RESOLUTION)) {
+ String id = parser.getAttributeValue(null, ATTR_ID);
+ label = parser.getAttributeValue(null, ATTR_LABEL);
+ final int horizontalDpi = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_HORIZONTAL_DPI));
+ final int verticalDpi = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_VERTICAL_DPI));
+ Resolution resolution = new Resolution(id, label, horizontalDpi, verticalDpi);
+ builder.setResolution(resolution);
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_RESOLUTION);
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ if (accept(parser, XmlPullParser.START_TAG, TAG_MARGINS)) {
+ final int leftMils = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_LEFT_MILS));
+ final int topMils = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_TOP_MILS));
+ final int rightMils = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_RIGHT_MILS));
+ final int bottomMils = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_BOTTOM_MILS));
+ Margins margins = new Margins(leftMils, topMils, rightMils, bottomMils);
+ builder.setMargins(margins);
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_MARGINS);
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ if (accept(parser, XmlPullParser.START_TAG, TAG_INPUT_TRAY)) {
+ String id = parser.getAttributeValue(null, ATTR_ID);
+ label = parser.getAttributeValue(null, ATTR_LABEL);
+ Tray tray = new Tray(id, label);
+ builder.setInputTray(tray);
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_INPUT_TRAY);
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ if (accept(parser, XmlPullParser.START_TAG, TAG_OUTPUT_TRAY)) {
+ String id = parser.getAttributeValue(null, ATTR_ID);
+ label = parser.getAttributeValue(null, ATTR_LABEL);
+ Tray tray = new Tray(id, label);
+ builder.setOutputTray(tray);
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_OUTPUT_TRAY);
+ parser.next();
+ }
+
+ printJob.setAttributes(builder.create());
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_ATTRIBUTES);
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ if (accept(parser, XmlPullParser.START_TAG, TAG_DOCUMENT_INFO)) {
+ String name = parser.getAttributeValue(null, ATTR_NAME);
+ final int pageCount = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_PAGE_COUNT));
+ final int contentType = Integer.parseInt(parser.getAttributeValue(null,
+ ATTR_CONTENT_TYPE));
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(name)
+ .setPageCount(pageCount)
+ .setContentType(contentType).create();
+ printJob.setDocumentInfo(info);
+ parser.next();
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_DOCUMENT_INFO);
+ parser.next();
+ }
+
+ mPrintJobs.add(printJob);
+
+ if (DEBUG_PERSISTENCE) {
+ Log.i(LOG_TAG, "[RESTORED] " + printJob);
+ }
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_JOB);
+
+ return true;
+ }
+
+ private void expect(XmlPullParser parser, int type, String tag)
+ throws IOException, XmlPullParserException {
+ if (!accept(parser, type, tag)) {
+ throw new XmlPullParserException("Exepected event: " + type
+ + " and tag: " + tag + " but got event: " + parser.getEventType()
+ + " and tag:" + parser.getName());
+ }
+ }
+
+ private void skipEmptyTextTags(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ while (accept(parser, XmlPullParser.TEXT, null)
+ && "\n".equals(parser.getText())) {
+ parser.next();
+ }
+ }
+
+ private boolean accept(XmlPullParser parser, int type, String tag)
+ throws IOException, XmlPullParserException {
+ if (parser.getEventType() != type) {
+ return false;
+ }
+ if (tag != null) {
+ if (!tag.equals(parser.getName())) {
+ return false;
+ }
+ } else if (parser.getName() != null) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ public static abstract class PrinterDiscoverySession {
+
+ private PrintSpoolerService mService;
+
+ private boolean mIsStarted;
+
+ public PrinterDiscoverySession() {
+ mService = PrintSpoolerService.peekInstance();
+ mService.createPrinterDiscoverySession();
+ mService.setPrinterDiscoverySessionClient(this);
+ }
+
+ public final void startPrinterDisovery(List<PrinterId> priorityList) {
+ mIsStarted = true;
+ mService.startPrinterDiscovery(priorityList);
+ }
+
+ public final void stopPrinterDiscovery() {
+ mIsStarted = false;
+ mService.stopPrinterDiscovery();
+ }
+
+ public void requestPrinterUpdated(PrinterId printerId) {
+ mService.requestPrinterUpdate(printerId);
+ }
+
+ public final void destroy() {
+ mService.setPrinterDiscoverySessionClient(null);
+ mService.destroyPrinterDiscoverySession();
+ }
+
+ public final boolean isStarted() {
+ return mIsStarted;
+ }
+
+ public abstract void onPrintersAdded(List<PrinterInfo> printers);
+
+ public abstract void onPrintersRemoved(List<PrinterId> printerIds);
+
+ public abstract void onPrintersUpdated(List<PrinterInfo> printers);
+ }
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java
new file mode 100644
index 0000000..141dbd1
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterActivity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013 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.printspooler;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.print.PrinterId;
+
+import com.android.printspooler.SelectPrinterFragment.OnPrinterSelectedListener;
+
+public class SelectPrinterActivity extends Activity implements OnPrinterSelectedListener {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.select_printer_activity);
+ }
+
+ @Override
+ public void onPrinterSelected(PrinterId printer) {
+ Intent intent = new Intent();
+ intent.putExtra(PrintJobConfigActivity.INTENT_EXTRA_PRINTER_ID, printer);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java
new file mode 100644
index 0000000..9ca3a86
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2013 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.printspooler;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.app.ListFragment;
+import android.app.LoaderManager;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.Loader;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
+import android.printservice.PrintServiceInfo;
+import android.text.TextUtils;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ListView;
+import android.widget.SearchView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is a fragment for selecting a printer.
+ */
+public final class SelectPrinterFragment extends ListFragment {
+
+ private static final int LOADER_ID_PRINTERS_LOADER = 1;
+
+ private static final String FRAGMRNT_TAG_ADD_PRINTER_DIALOG =
+ "FRAGMRNT_TAG_ADD_PRINTER_DIALOG";
+
+ private static final String FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS =
+ "FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS";
+
+ private final ArrayList<PrintServiceInfo> mAddPrinterServices =
+ new ArrayList<PrintServiceInfo>();
+
+ public static interface OnPrinterSelectedListener {
+ public void onPrinterSelected(PrinterId printerId);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ setListAdapter(new DestinationAdapter());
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ inflater.inflate(R.menu.select_printer_activity, menu);
+
+ MenuItem searchItem = menu.findItem(R.id.action_search);
+ SearchView searchView = (SearchView) searchItem.getActionView();
+ searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String searchString) {
+ ((DestinationAdapter) getListAdapter()).getFilter().filter(searchString);
+ return true;
+ }
+ });
+
+ if (mAddPrinterServices.isEmpty()) {
+ menu.removeItem(R.id.action_add_printer);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ updateAddPrintersAdapter();
+ getActivity().invalidateOptionsMenu();
+ super.onResume();
+ }
+
+ @Override
+ public void onListItemClick(ListView list, View view, int position, long id) {
+ PrinterInfo printer = (PrinterInfo) list.getAdapter().getItem(position);
+ Activity activity = getActivity();
+ if (activity instanceof OnPrinterSelectedListener) {
+ ((OnPrinterSelectedListener) activity).onPrinterSelected(printer.getId());
+ } else {
+ throw new IllegalStateException("the host activity must implement"
+ + " OnPrinterSelectedListener");
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == R.id.action_add_printer) {
+ showAddPrinterSelectionDialog();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void updateAddPrintersAdapter() {
+ mAddPrinterServices.clear();
+
+ // Get all print services.
+ List<ResolveInfo> resolveInfos = getActivity().getPackageManager().queryIntentServices(
+ new Intent(android.printservice.PrintService.SERVICE_INTERFACE),
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+
+ // No print services - done.
+ if (resolveInfos.isEmpty()) {
+ return;
+ }
+
+ // Find the services with valid add printers activities.
+ final int resolveInfoCount = resolveInfos.size();
+ for (int i = 0; i < resolveInfoCount; i++) {
+ ResolveInfo resolveInfo = resolveInfos.get(i);
+
+ PrintServiceInfo printServiceInfo = PrintServiceInfo.create(
+ resolveInfo, getActivity());
+ String addPrintersActivity = printServiceInfo.getAddPrintersActivityName();
+
+ // No add printers activity declared - done.
+ if (TextUtils.isEmpty(addPrintersActivity)) {
+ continue;
+ }
+
+ ComponentName addPrintersComponentName = new ComponentName(
+ resolveInfo.serviceInfo.packageName,
+ addPrintersActivity);
+ Intent addPritnersIntent = new Intent(Intent.ACTION_MAIN)
+ .setComponent(addPrintersComponentName);
+
+ // The add printers activity is valid - add it.
+ if (!getActivity().getPackageManager().queryIntentActivities(
+ addPritnersIntent, 0).isEmpty()) {
+ mAddPrinterServices.add(printServiceInfo);
+ }
+ }
+ }
+
+ private void showAddPrinterSelectionDialog() {
+ FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ Fragment oldFragment = getFragmentManager().findFragmentByTag(
+ FRAGMRNT_TAG_ADD_PRINTER_DIALOG);
+ if (oldFragment != null) {
+ transaction.remove(oldFragment);
+ }
+ AddPrinterAlertDialogFragment newFragment = new AddPrinterAlertDialogFragment();
+ Bundle arguments = new Bundle();
+ arguments.putParcelableArrayList(FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS,
+ mAddPrinterServices);
+ newFragment.setArguments(arguments);
+ transaction.add(newFragment, FRAGMRNT_TAG_ADD_PRINTER_DIALOG);
+ transaction.commit();
+ }
+
+ public static class AddPrinterAlertDialogFragment extends DialogFragment {
+
+ private static final String DEFAULT_MARKET_QUERY_STRING =
+ "market://search?q=print";
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.choose_print_service);
+
+ final List<PrintServiceInfo> printServices = (List<PrintServiceInfo>) (List<?>)
+ getArguments().getParcelableArrayList(FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS);
+
+ ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
+ android.R.layout.simple_list_item_1);
+ final int printServiceCount = printServices.size();
+ for (int i = 0; i < printServiceCount; i++) {
+ PrintServiceInfo printService = printServices.get(i);
+ adapter.add(printService.getResolveInfo().loadLabel(
+ getActivity().getPackageManager()).toString());
+ }
+
+ builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ PrintServiceInfo printService = printServices.get(which);
+ ComponentName componentName = new ComponentName(
+ printService.getResolveInfo().serviceInfo.packageName,
+ printService.getAddPrintersActivityName());
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setComponent(componentName);
+ startActivity(intent);
+ }
+ });
+
+ Uri marketUri = Uri.parse(DEFAULT_MARKET_QUERY_STRING);
+ final Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri);
+ if (getActivity().getPackageManager().resolveActivity(marketIntent, 0) != null) {
+ builder.setPositiveButton(R.string.search_play_store,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ startActivity(marketIntent);
+ }
+ });
+ }
+
+ return builder.create();
+ }
+ }
+
+ private final class DestinationAdapter extends BaseAdapter
+ implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>, Filterable {
+
+ private final Object mLock = new Object();
+
+ private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
+
+ private final List<PrinterInfo> mFilteredPrinters = new ArrayList<PrinterInfo>();
+
+ private CharSequence mLastSearchString;
+
+ public DestinationAdapter() {
+ getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
+ }
+
+ @Override
+ public Filter getFilter() {
+ return new Filter() {
+ @Override
+ protected FilterResults performFiltering(CharSequence constraint) {
+ synchronized (mLock) {
+ if (TextUtils.isEmpty(constraint)) {
+ return null;
+ }
+ FilterResults results = new FilterResults();
+ List<PrinterInfo> filteredPrinters = new ArrayList<PrinterInfo>();
+ String constraintLowerCase = constraint.toString().toLowerCase();
+ final int printerCount = mPrinters.size();
+ for (int i = 0; i < printerCount; i++) {
+ PrinterInfo printer = mPrinters.get(i);
+ if (printer.getName().toLowerCase().contains(constraintLowerCase)) {
+ filteredPrinters.add(printer);
+ }
+ }
+ results.values = filteredPrinters;
+ results.count = filteredPrinters.size();
+ return results;
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected void publishResults(CharSequence constraint, FilterResults results) {
+ synchronized (mLock) {
+ mLastSearchString = constraint;
+ mFilteredPrinters.clear();
+ if (results == null) {
+ mFilteredPrinters.addAll(mPrinters);
+ } else {
+ List<PrinterInfo> printers = (List<PrinterInfo>) results.values;
+ mFilteredPrinters.addAll(printers);
+ }
+ }
+ notifyDataSetChanged();
+ }
+ };
+ }
+
+ @Override
+ public int getCount() {
+ synchronized (mLock) {
+ return mFilteredPrinters.size();
+ }
+ }
+
+ @Override
+ public Object getItem(int position) {
+ synchronized (mLock) {
+ return mFilteredPrinters.get(position);
+ }
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getDropDownView(int position, View convertView,
+ ViewGroup parent) {
+ return getView(position, convertView, parent);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = getActivity().getLayoutInflater().inflate(
+ R.layout.spinner_dropdown_item, parent, false);
+ }
+
+ CharSequence title = null;
+ CharSequence subtitle = null;
+
+ PrinterInfo printer = (PrinterInfo) getItem(position);
+ title = printer.getName();
+ try {
+ PackageManager pm = getActivity().getPackageManager();
+ PackageInfo packageInfo = pm.getPackageInfo(printer.getId()
+ .getServiceName().getPackageName(), 0);
+ subtitle = packageInfo.applicationInfo.loadLabel(pm);
+ } catch (NameNotFoundException nnfe) {
+ /* ignore */
+ }
+
+ TextView titleView = (TextView) convertView.findViewById(R.id.title);
+ titleView.setText(title);
+
+ TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
+ if (!TextUtils.isEmpty(subtitle)) {
+ subtitleView.setText(subtitle);
+ subtitleView.setVisibility(View.VISIBLE);
+ } else {
+ subtitleView.setText(null);
+ subtitleView.setVisibility(View.GONE);
+ }
+
+ return convertView;
+ }
+
+ @Override
+ public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
+ if (id == LOADER_ID_PRINTERS_LOADER) {
+ return new FusedPrintersProvider(getActivity());
+ }
+ return null;
+ }
+
+ @Override
+ public void onLoadFinished(Loader<List<PrinterInfo>> loader,
+ List<PrinterInfo> printers) {
+ synchronized (mLock) {
+ mPrinters.clear();
+ mPrinters.addAll(printers);
+ mFilteredPrinters.clear();
+ mFilteredPrinters.addAll(printers);
+ if (!TextUtils.isEmpty(mLastSearchString)) {
+ getFilter().filter(mLastSearchString);
+ }
+ }
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public void onLoaderReset(Loader<List<PrinterInfo>> loader) {
+ synchronized (mLock) {
+ mPrinters.clear();
+ mFilteredPrinters.clear();
+ }
+ notifyDataSetInvalidated();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index fb2348e..34e3013 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -430,7 +430,8 @@
@Override
public void onServiceStateChanged(ServiceState state) {
if (DEBUG) {
- Log.d(TAG, "onServiceStateChanged state=" + state.getState());
+ Log.d(TAG, "onServiceStateChanged voiceState=" + state.getVoiceRegState()
+ + " dataState=" + state.getDataRegState());
}
mServiceState = state;
updateTelephonySignalStrength();
@@ -506,10 +507,16 @@
private boolean hasService() {
if (mServiceState != null) {
- switch (mServiceState.getState()) {
- case ServiceState.STATE_OUT_OF_SERVICE:
+ // Consider the device to be in service if either voice or data service is available.
+ // Some SIM cards are marketed as data-only and do not support voice service, and on
+ // these SIM cards, we want to show signal bars for data service as well as the "no
+ // service" or "emergency calls only" text that indicates that voice is not available.
+ switch(mServiceState.getVoiceRegState()) {
case ServiceState.STATE_POWER_OFF:
return false;
+ case ServiceState.STATE_OUT_OF_SERVICE:
+ case ServiceState.STATE_EMERGENCY_ONLY:
+ return mServiceState.getDataRegState() == ServiceState.STATE_IN_SERVICE;
default:
return true;
}
diff --git a/packages/services/PacProcessor/Android.mk b/packages/services/PacProcessor/Android.mk
index e4afde6..d9566d5 100644
--- a/packages/services/PacProcessor/Android.mk
+++ b/packages/services/PacProcessor/Android.mk
@@ -18,24 +18,15 @@
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := \
- main_pacserver.cpp \
- ProxyService.cpp \
- IProxyService.cpp
-
-LOCAL_C_INCLUDES += \
- external/chromium-libpac/src
-
-LOCAL_SHARED_LIBRARIES := \
- libutils \
- liblog \
- libpac \
- libbinder \
- libstlport
-
-LOCAL_MODULE := pacserver
LOCAL_MODULE_TAGS := optional
-include external/stlport/libstlport.mk
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
-include $(BUILD_EXECUTABLE)
+LOCAL_PACKAGE_NAME := PacProcessor
+LOCAL_CERTIFICATE := platform
+
+LOCAL_REQUIRED_MODULES := libjni_pacprocessor
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/packages/services/PacProcessor/AndroidManifest.xml b/packages/services/PacProcessor/AndroidManifest.xml
new file mode 100644
index 0000000..6740c16
--- /dev/null
+++ b/packages/services/PacProcessor/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.pacprocessor">
+
+ <uses-permission android:name="android.permission.INTERNET" />
+
+ <application
+ android:label="@string/app_name">
+
+ <service android:name=".PacService"
+ android:exported="true">
+ </service>
+
+ </application>
+
+</manifest>
diff --git a/packages/services/PacProcessor/IProxyService.cpp b/packages/services/PacProcessor/IProxyService.cpp
deleted file mode 100644
index 3707d85..0000000
--- a/packages/services/PacProcessor/IProxyService.cpp
+++ /dev/null
@@ -1,97 +0,0 @@
-#define LOG_TAG "ProxyTesting"
-
-#include <stdint.h>
-#include <sys/types.h>
-#include <binder/Parcel.h>
-#include <binder/IPCThreadState.h>
-#include <utils/Errors.h>
-#include "IProxyService.h"
-
-#include <utils/Log.h>
-
-#include <private/android_filesystem_config.h>
-
-using namespace android;
-
-String16 BpProxyService::resolveProxies(String16 host, String16 url) {
- String16 ret;
- return ret;
-}
-
-void BpProxyService::setPacFile(String16& scriptContents) {
-
-}
-
-void BpProxyService::startPacSystem() {
-
-}
-void BpProxyService::stopPacSystem() {
-
-}
-
-IMPLEMENT_META_INTERFACE(ProxyService, "com.android.net.IProxyService");
-
-status_t BnProxyService::onTransact(
- uint32_t code, const Parcel& data,
- Parcel* reply, uint32_t flags) {
- int returnInt = 0;
- switch (code) {
- case RESOLVE_PROXIES:
- {
- CHECK_INTERFACE(IProxyService, data, reply);
- String16 host = data.readString16();
- String16 url = data.readString16();
- String16 response = resolveProxies(host, url);
- reply->writeNoException();
- reply->writeString16(response);
- return NO_ERROR;
- } break;
- case SET_PAC:
- {
- CHECK_INTERFACE(IProxyService, data, reply);
- if (notSystemUid()) {
- returnInt = 1;
- } else {
- String16 pacFile = data.readString16();
- setPacFile(pacFile);
- }
- reply->writeNoException();
- reply->writeInt32(returnInt);
- return NO_ERROR;
- } break;
- case START_PAC:
- {
- CHECK_INTERFACE(IProxyService, data, reply);
- if (notSystemUid()) {
- returnInt = 1;
- } else {
- startPacSystem();
- }
- reply->writeNoException();
- reply->writeInt32(returnInt);
- return NO_ERROR;
- } break;
- case STOP_PAC:
- {
- CHECK_INTERFACE(IProxyService, data, reply);
- if (notSystemUid()) {
- returnInt = 1;
- } else {
- stopPacSystem();
- }
- reply->writeNoException();
- reply->writeInt32(returnInt);
- return NO_ERROR;
- } break;
- default:
- return BBinder::onTransact(code, data, reply, flags);
- }
-}
-
-int BnProxyService::getCallingUid() {
- return IPCThreadState::self()->getCallingUid();
-}
-
-bool BnProxyService::notSystemUid() {
- return getCallingUid() != AID_SYSTEM;
-}
diff --git a/packages/services/PacProcessor/IProxyService.h b/packages/services/PacProcessor/IProxyService.h
deleted file mode 100644
index 57c527b..0000000
--- a/packages/services/PacProcessor/IProxyService.h
+++ /dev/null
@@ -1,59 +0,0 @@
-#ifndef IPROXY_SERVICE_H
-#define IPROXY_SERVICE_H
-
-#include <binder/IInterface.h>
-#include <binder/IBinder.h>
-
-namespace android {
-class IProxyService : public IInterface {
-public:
- /**
- * Keep up-to-date with
- * frameworks/base/packages/services/Proxy/com/android/net/IProxyService.aidl
- */
- enum {
- RESOLVE_PROXIES = IBinder::FIRST_CALL_TRANSACTION,
- SET_PAC,
- START_PAC,
- STOP_PAC,
- };
-public:
- DECLARE_META_INTERFACE(ProxyService);
-
-public:
-
- virtual String16 resolveProxies(String16 host, String16 url) = 0;
-
- virtual void setPacFile(String16& scriptContents) = 0;
-
- virtual void startPacSystem() = 0;
- virtual void stopPacSystem() = 0;
-private:
-};
-
-class BpProxyService : public BpInterface<IProxyService> {
-public:
- BpProxyService(const sp<IBinder>& impl) : BpInterface<IProxyService>(impl) {}
-
- virtual String16 resolveProxies(String16 host, String16 url);
-
- virtual void setPacFile(String16& scriptContents);
-
- virtual void startPacSystem();
- virtual void stopPacSystem();
-};
-
-class BnProxyService : public BnInterface<IProxyService> {
-public:
- virtual status_t onTransact(
- uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0);
-
-private:
- int getCallingUid();
-
- bool notSystemUid();
-};
-}
-
-
-#endif //IPROXY_SERVICE_H
diff --git a/packages/services/PacProcessor/ProxyService.cpp b/packages/services/PacProcessor/ProxyService.cpp
deleted file mode 100644
index 7084a47..0000000
--- a/packages/services/PacProcessor/ProxyService.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-#define LOG_TAG "ProxyService"
-#include <utils/Log.h>
-
-#include <errno.h>
-#include <utils/threads.h>
-#include <binder/IServiceManager.h>
-#include <binder/IPCThreadState.h>
-#include <sys/stat.h>
-#include <proxy_resolver_v8.h>
-#include <sstream>
-
-#include "ProxyService.h"
-
-using namespace net;
-
-using namespace android;
-
-class ProxyErrorLogger : public ProxyErrorListener {
-protected:
- ~ProxyErrorLogger() {
-
- }
-public:
- void AlertMessage(String16 message) {
- String8 str(message);
- ALOGD("Alert: %s", str.string());
- }
- void ErrorMessage(String16 message) {
- String8 str(message);
- ALOGE("Error: %s", str.string());
- }
-};
-
-void ProxyService::instantiate() {
- ALOGV("instantiate");
- defaultServiceManager()->addService(String16("com.android.net.IProxyService"),
- new ProxyService());
-}
-
-ProxyService::ProxyService() {
- hasSetScript = false;
-}
-
-ProxyService::~ProxyService() {
- stopPacSystem();
-}
-
-String16 ProxyService::resolveProxies(String16 host, String16 url) {
- ALOGV("resolve");
- String16 blankRet;
- if (proxyResolver != NULL) {
- if (hasSetScript) {
- String16 ret;
- if (proxyResolver->GetProxyForURL(url, host, &ret) != OK) {
- return blankRet;
- }
- return ret;
- } else {
- ALOGD("Unable to resolve PAC when no script is set!");
- }
- } else {
- ALOGE("Cannot parse while resolver not initialized!");
- }
- return blankRet;
-}
-
-void ProxyService::setPacFile(String16& scriptContents) {
- ALOGV("set");
- if (proxyResolver != NULL) {
- if (proxyResolver->SetPacScript(scriptContents) != OK) {
- ALOGD("Unable to initialize PAC - Resolving will not work");
- } else {
- hasSetScript = true;
- }
- } else {
- ALOGE("PAC script set while resolver not initialized!");
- }
-}
-
-void ProxyService::startPacSystem() {
- ALOGV("start");
- // Stop in case redundant start call
- stopPacSystem();
-
- proxyResolver = new ProxyResolverV8(ProxyResolverJSBindings::CreateDefault(),
- new ProxyErrorLogger());
- hasSetScript = false;
-}
-
-void ProxyService::stopPacSystem() {
- ALOGV("stop");
- if (proxyResolver != NULL) {
- delete proxyResolver;
- proxyResolver = NULL;
- }
-}
diff --git a/packages/services/PacProcessor/ProxyService.h b/packages/services/PacProcessor/ProxyService.h
deleted file mode 100644
index a0861b2..0000000
--- a/packages/services/PacProcessor/ProxyService.h
+++ /dev/null
@@ -1,33 +0,0 @@
-#ifndef PROXY_SERVICE_H
-#define PROXY_SERVICE_H
-
-#include <binder/IInterface.h>
-#include "IProxyService.h"
-#include "proxy_resolver_v8.h"
-
-namespace android {
-
-class ProxyService : public BnProxyService {
-public:
- static void instantiate();
-
-private:
- ProxyService();
- virtual ~ProxyService();
-
-public:
- String16 resolveProxies(String16 host, String16 url);
-
- void setPacFile(String16& scriptContents);
-
- void startPacSystem();
- void stopPacSystem();
-
-private:
- net::ProxyResolverV8* proxyResolver;
- bool hasSetScript;
-};
-
-}
-
-#endif //PROXY_SERVICE_H
diff --git a/packages/services/PacProcessor/com/android/net/IProxyService.aidl b/packages/services/PacProcessor/com/android/net/IProxyService.aidl
new file mode 100644
index 0000000..4e54aba
--- /dev/null
+++ b/packages/services/PacProcessor/com/android/net/IProxyService.aidl
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2013, 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.net;
+
+/** @hide */
+interface IProxyService
+{
+ String resolvePacFile(String host, String url);
+
+ oneway void setPacFile(String scriptContents);
+
+ oneway void startPacSystem();
+ oneway void stopPacSystem();
+}
diff --git a/packages/services/PacProcessor/jni/Android.mk b/packages/services/PacProcessor/jni/Android.mk
new file mode 100644
index 0000000..f16c90b
--- /dev/null
+++ b/packages/services/PacProcessor/jni/Android.mk
@@ -0,0 +1,41 @@
+#
+# Copyright (C) 2013 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ jni_init.cpp \
+ com_android_pacprocessor_PacNative.cpp
+
+LOCAL_C_INCLUDES += \
+ external/chromium-libpac/src
+
+LOCAL_SHARED_LIBRARIES := \
+ libandroidfw \
+ libandroid_runtime \
+ liblog \
+ libutils \
+ libnativehelper \
+ libpac
+
+LOCAL_MODULE := libjni_pacprocessor
+LOCAL_MODULE_TAGS := optional
+
+include external/stlport/libstlport.mk
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp b/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
new file mode 100644
index 0000000..c5aa13b
--- /dev/null
+++ b/packages/services/PacProcessor/jni/com_android_pacprocessor_PacNative.cpp
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#define LOG_TAG "PacProcessor"
+
+#include <utils/Log.h>
+#include <utils/Mutex.h>
+#include "android_runtime/AndroidRuntime.h"
+
+#include "jni.h"
+#include "JNIHelp.h"
+
+#include "proxy_resolver_v8.h"
+
+namespace android {
+
+class ProxyErrorLogger : public net::ProxyErrorListener {
+public:
+ ~ProxyErrorLogger() {
+
+ }
+ void AlertMessage(String16 message) {
+ String8 str(message);
+ ALOGD("Alert: %s", str.string());
+ }
+ void ErrorMessage(String16 message) {
+ String8 str(message);
+ ALOGE("Error: %s", str.string());
+ }
+};
+
+net::ProxyResolverV8* proxyResolver = NULL;
+ProxyErrorLogger* logger = NULL;
+bool pacSet = false;
+
+String16 jstringToString16(JNIEnv* env, jstring jstr) {
+ const jchar* str = env->GetStringCritical(jstr, 0);
+ String16 str16(str, env->GetStringLength(jstr));
+ env->ReleaseStringCritical(jstr, str);
+ return str16;
+}
+
+jstring string16ToJstring(JNIEnv* env, String16 string) {
+ const char16_t* str = string.string();
+ size_t len = string.size();
+
+ return env->NewString(str, len);
+}
+
+static jboolean com_android_pacprocessor_PacNative_createV8ParserNativeLocked(JNIEnv* env,
+ jobject) {
+ if (proxyResolver == NULL) {
+ logger = new ProxyErrorLogger();
+ proxyResolver = new net::ProxyResolverV8(net::ProxyResolverJSBindings::CreateDefault(),
+ logger);
+ pacSet = false;
+ return JNI_FALSE;
+ }
+ return JNI_TRUE;
+}
+
+static jboolean com_android_pacprocessor_PacNative_destroyV8ParserNativeLocked(JNIEnv* env,
+ jobject) {
+ if (proxyResolver != NULL) {
+ delete logger;
+ delete proxyResolver;
+ logger = NULL;
+ proxyResolver = NULL;
+ return JNI_FALSE;
+ }
+ return JNI_TRUE;
+}
+
+static jboolean com_android_pacprocessor_PacNative_setProxyScriptNativeLocked(JNIEnv* env, jobject,
+ jstring script) {
+ String16 script16 = jstringToString16(env, script);
+
+ if (proxyResolver == NULL) {
+ ALOGE("V8 Parser not started when setting PAC script");
+ return JNI_TRUE;
+ }
+
+ if (proxyResolver->SetPacScript(script16) != OK) {
+ ALOGE("Unable to set PAC script");
+ return JNI_TRUE;
+ }
+ pacSet = true;
+
+ return JNI_FALSE;
+}
+
+static jstring com_android_pacprocessor_PacNative_makeProxyRequestNativeLocked(JNIEnv* env, jobject,
+ jstring url, jstring host) {
+ String16 url16 = jstringToString16(env, url);
+ String16 host16 = jstringToString16(env, host);
+ String16 ret;
+
+ if (proxyResolver == NULL) {
+ ALOGE("V8 Parser not initialized when running PAC script");
+ return NULL;
+ }
+
+ if (!pacSet) {
+ ALOGW("Attempting to run PAC with no script set");
+ return NULL;
+ }
+
+ if (proxyResolver->GetProxyForURL(url16, host16, &ret) != OK) {
+ String8 ret8(ret);
+ ALOGE("Error Running PAC: %s", ret8.string());
+ return NULL;
+ }
+
+ jstring jret = string16ToJstring(env, ret);
+
+ return jret;
+}
+
+static JNINativeMethod gMethods[] = {
+ { "createV8ParserNativeLocked", "()Z",
+ (void*)com_android_pacprocessor_PacNative_createV8ParserNativeLocked},
+ { "destroyV8ParserNativeLocked", "()Z",
+ (void*)com_android_pacprocessor_PacNative_destroyV8ParserNativeLocked},
+ { "setProxyScriptNativeLocked", "(Ljava/lang/String;)Z",
+ (void*)com_android_pacprocessor_PacNative_setProxyScriptNativeLocked},
+ { "makeProxyRequestNativeLocked", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
+ (void*)com_android_pacprocessor_PacNative_makeProxyRequestNativeLocked},
+};
+
+int register_com_android_pacprocessor_PacNative(JNIEnv* env) {
+ return jniRegisterNativeMethods(env, "com/android/pacprocessor/PacNative",
+ gMethods, NELEM(gMethods));
+}
+
+} /* namespace android */
diff --git a/packages/services/PacProcessor/jni/jni_init.cpp b/packages/services/PacProcessor/jni/jni_init.cpp
new file mode 100644
index 0000000..bda33fb
--- /dev/null
+++ b/packages/services/PacProcessor/jni/jni_init.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+
+#define LOG_TAG "PacProcessor"
+
+#include <utils/Log.h>
+#include "jni.h"
+
+namespace android {
+ extern int register_com_android_pacprocessor_PacNative(JNIEnv *env);
+}
+
+using namespace android;
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ JNIEnv *env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ ALOGE("ERROR: GetEnv failed");
+ return -1;
+ }
+
+ register_com_android_pacprocessor_PacNative(env);
+
+ return JNI_VERSION_1_6;
+}
diff --git a/packages/services/PacProcessor/main_pacserver.cpp b/packages/services/PacProcessor/main_pacserver.cpp
deleted file mode 100644
index 19588b5..0000000
--- a/packages/services/PacProcessor/main_pacserver.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2010 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.
- */
-
-#define LOG_TAG "pacserver"
-//#define LOG_NDEBUG 0
-
-#include <binder/IPCThreadState.h>
-#include <binder/ProcessState.h>
-#include <binder/IServiceManager.h>
-#include <utils/Log.h>
-#include "ProxyService.h"
-#include "proxy_resolver_v8.h"
-#include <stdio.h>
-
-using namespace android;
-
-int main(int argc, char** argv)
-{
- sp<ProcessState> proc(ProcessState::self());
- sp<IServiceManager> sm = defaultServiceManager();
-
- printf("1\n");
- ALOGV("ServiceManager: %p", sm.get());
- ProxyService::instantiate();
- printf("1\n");
-
- ProcessState::self()->startThreadPool();
- printf("1\n");
- IPCThreadState::self()->joinThreadPool();
-}
diff --git a/packages/services/PacProcessor/res/values/strings.xml b/packages/services/PacProcessor/res/values/strings.xml
new file mode 100644
index 0000000..301a2b6
--- /dev/null
+++ b/packages/services/PacProcessor/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+ <string name="app_name">PacProcessor</string>
+
+</resources>
diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java
new file mode 100644
index 0000000..c67fe9f
--- /dev/null
+++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacNative.java
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) 2013, 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.pacprocessor;
+
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class PacNative {
+ private static final String TAG = "PacProxy";
+
+ private String mCurrentPac;
+
+ private boolean mIsActive;
+
+ // Only make native calls from inside synchronized blocks.
+ private native boolean createV8ParserNativeLocked();
+ private native boolean destroyV8ParserNativeLocked();
+
+ private native boolean setProxyScriptNativeLocked(String script);
+
+ private native String makeProxyRequestNativeLocked(String url, String host);
+
+ static {
+ System.loadLibrary("jni_pacprocessor");
+ }
+
+ PacNative() {
+
+ }
+
+ public synchronized boolean startPacSupport() {
+ if (createV8ParserNativeLocked()) {
+ Log.e(TAG, "Unable to Create v8 Proxy Parser.");
+ return true;
+ }
+ mIsActive = true;
+ return false;
+ }
+
+ public synchronized boolean stopPacSupport() {
+ if (mIsActive) {
+ if (destroyV8ParserNativeLocked()) {
+ Log.e(TAG, "Unable to Destroy v8 Proxy Parser.");
+ return true;
+ }
+ mIsActive = false;
+ }
+ return false;
+ }
+
+ public synchronized boolean setCurrentProxyScript(String script) {
+ if (setProxyScriptNativeLocked(script)) {
+ Log.e(TAG, "Unable to parse proxy script.");
+ return true;
+ }
+ return false;
+ }
+
+ public synchronized String makeProxyRequest(String url, String host) {
+ String ret = makeProxyRequestNativeLocked(url, host);
+ if ((ret == null) || (ret.length() == 0)) {
+ Log.e(TAG, "v8 Proxy request failed.");
+ ret = null;
+ }
+ return ret;
+ }
+
+ public synchronized boolean isActive() {
+ return mIsActive;
+ }
+}
diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
new file mode 100644
index 0000000..7e76025
--- /dev/null
+++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java
@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) 2013, 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.pacprocessor;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.net.IProxyService;
+
+public class PacService extends Service {
+ private static final String TAG = "PacService";
+
+ private PacNative mPacNative;
+ private ProxyServiceStub mStub;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (mPacNative == null) {
+ mPacNative = new PacNative();
+ mStub = new ProxyServiceStub(mPacNative);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (mPacNative != null) {
+ mPacNative.stopPacSupport();
+ mPacNative = null;
+ mStub = null;
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (mPacNative == null) {
+ mPacNative = new PacNative();
+ mStub = new ProxyServiceStub(mPacNative);
+ }
+ return mStub;
+ }
+
+ private static class ProxyServiceStub extends IProxyService.Stub {
+ private final PacNative mPacNative;
+
+ public ProxyServiceStub(PacNative pacNative) {
+ mPacNative = pacNative;
+ }
+
+ @Override
+ public String resolvePacFile(String host, String url) throws RemoteException {
+ return mPacNative.makeProxyRequest(url, host);
+ }
+
+ @Override
+ public void setPacFile(String script) throws RemoteException {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ Log.e(TAG, "Only system user is allowed to call setPacFile");
+ throw new SecurityException();
+ }
+ mPacNative.setCurrentProxyScript(script);
+ }
+
+ @Override
+ public void startPacSystem() throws RemoteException {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ Log.e(TAG, "Only system user is allowed to call startPacSystem");
+ throw new SecurityException();
+ }
+ mPacNative.startPacSupport();
+ }
+
+ @Override
+ public void stopPacSystem() throws RemoteException {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ Log.e(TAG, "Only system user is allowed to call stopPacSystem");
+ throw new SecurityException();
+ }
+ mPacNative.stopPacSupport();
+ }
+ }
+}
diff --git a/packages/services/Proxy/AndroidManifest.xml b/packages/services/Proxy/AndroidManifest.xml
index 02475c0..09b8327 100644
--- a/packages/services/Proxy/AndroidManifest.xml
+++ b/packages/services/Proxy/AndroidManifest.xml
@@ -6,7 +6,6 @@
<uses-permission android:name="android.permission.INTERNET" />
<application
- android:persistent="true"
android:label="@string/app_label"
android:process="com.android.proxyhandler">
diff --git a/packages/services/Proxy/com/android/net/IProxyService.aidl b/packages/services/Proxy/com/android/net/IProxyService.aidl
deleted file mode 100644
index 7e9ed79..0000000
--- a/packages/services/Proxy/com/android/net/IProxyService.aidl
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.android.net;
-
-/** @hide */
-interface IProxyService
-{
- /**
- * Keep up-to-date with
- * frameworks/base/packages/services/PacProcessor/IProxyService.h
- */
- String resolvePacFile(String host, String url);
-
- int setPacFile(String scriptContents);
-
- int startPacSystem();
- int stopPacSystem();
-}
diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java
index 0aea5ee1..18ed645 100644
--- a/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java
+++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyService.java
@@ -24,24 +24,28 @@
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
- handleCommand(intent);
+ if (handleCommand(intent)) {
+ return START_REDELIVER_INTENT;
+ }
}
- return START_STICKY;
+ return START_NOT_STICKY;
}
- private void handleCommand(Intent intent) {
+ private boolean handleCommand(Intent intent) {
Bundle bundle = intent.getExtras();
ProxyProperties proxy = null;
if ((bundle != null) && bundle.containsKey(Proxy.EXTRA_PROXY_INFO)) {
proxy = bundle.getParcelable(Proxy.EXTRA_PROXY_INFO);
if ((proxy != null) && !TextUtils.isEmpty(proxy.getPacFileUrl())) {
startProxy(proxy);
+ return true;
} else {
stopSelf();
}
} else {
stopSelf();
}
+ return false;
}
diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServiceReceiver.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServiceReceiver.java
index f5c2ca5..4638def 100644
--- a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServiceReceiver.java
+++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServiceReceiver.java
@@ -4,7 +4,9 @@
import android.content.Context;
import android.content.Intent;
import android.net.Proxy;
+import android.net.ProxyProperties;
import android.os.Bundle;
+import android.text.TextUtils;
public class ProxyServiceReceiver extends BroadcastReceiver {
@@ -12,11 +14,16 @@
public void onReceive(Context context, Intent intent) {
Intent service = new Intent(context, ProxyService.class);
Bundle bundle = intent.getExtras();
+ ProxyProperties proxy = null;
if (bundle != null) {
- service.putExtra(Proxy.EXTRA_PROXY_INFO,
- bundle.getParcelable(Proxy.EXTRA_PROXY_INFO));
+ proxy = bundle.getParcelable(Proxy.EXTRA_PROXY_INFO);
+ service.putExtra(Proxy.EXTRA_PROXY_INFO, proxy);
}
- context.startService(service);
+ if ((proxy != null) && (!TextUtils.isEmpty(proxy.getPacFileUrl()))) {
+ context.startService(service);
+ } else {
+ context.stopService(service);
+ }
}
}
diff --git a/services/java/Android.mk b/services/java/Android.mk
index 95b28d9..8c3d0f0 100644
--- a/services/java/Android.mk
+++ b/services/java/Android.mk
@@ -11,7 +11,7 @@
LOCAL_MODULE:= services
-LOCAL_JAVA_LIBRARIES := android.policy telephony-common
+LOCAL_JAVA_LIBRARIES := android.policy conscrypt telephony-common
include $(BUILD_JAVA_LIBRARY)
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index 43f95c3..7e83396 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -16,11 +16,14 @@
package com.android.server;
+import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
+
import com.android.internal.os.storage.ExternalStorageFormatter;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.JournaledFile;
import com.android.internal.util.XmlUtils;
import com.android.internal.widget.LockPatternUtils;
+import com.android.org.conscrypt.TrustedCertificateStore;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -49,6 +52,7 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
@@ -66,7 +70,12 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.security.Credentials;
+import android.security.IKeyChainService;
+import android.security.KeyChain;
+import android.security.KeyChain.KeyChainConnection;
import android.util.AtomicFile;
+import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;
@@ -75,6 +84,7 @@
import android.view.IWindowManager;
import android.view.WindowManagerPolicy;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
@@ -82,8 +92,14 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
+import java.security.KeyStore.TrustedCertificateEntry;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
import java.text.DateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -1870,6 +1886,76 @@
return !"".equals(state);
}
+ public boolean installCaCert(byte[] certBuffer) throws RemoteException {
+ mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null);
+ KeyChainConnection keyChainConnection = null;
+ byte[] pemCert;
+ try {
+ X509Certificate cert = parseCert(certBuffer);
+ pemCert = Credentials.convertToPem(cert);
+ } catch (CertificateException ce) {
+ Log.e(TAG, "Problem converting cert", ce);
+ return false;
+ } catch (IOException ioe) {
+ Log.e(TAG, "Problem reading cert", ioe);
+ return false;
+ }
+ try {
+ keyChainConnection = KeyChain.bind(mContext);
+ try {
+ keyChainConnection.getService().installCaCertificate(pemCert);
+ return true;
+ } finally {
+ if (keyChainConnection != null) {
+ keyChainConnection.close();
+ keyChainConnection = null;
+ }
+ }
+ } catch (InterruptedException e1) {
+ Log.w(TAG, "installCaCertsToKeyChain(): ", e1);
+ Thread.currentThread().interrupt();
+ }
+ return false;
+ }
+
+ private static X509Certificate parseCert(byte[] certBuffer)
+ throws CertificateException, IOException {
+ CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+ return (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(
+ certBuffer));
+ }
+
+ public void uninstallCaCert(final byte[] certBuffer) {
+ mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null);
+ TrustedCertificateStore certStore = new TrustedCertificateStore();
+ String alias = null;
+ try {
+ X509Certificate cert = parseCert(certBuffer);
+ alias = certStore.getCertificateAlias(cert);
+ } catch (CertificateException ce) {
+ Log.e(TAG, "Problem creating X509Certificate", ce);
+ return;
+ } catch (IOException ioe) {
+ Log.e(TAG, "Problem reading certificate", ioe);
+ return;
+ }
+ try {
+ KeyChainConnection keyChainConnection = KeyChain.bind(mContext);
+ IKeyChainService service = keyChainConnection.getService();
+ try {
+ service.deleteCaCertificate(alias);
+ } catch (RemoteException e) {
+ Log.e(TAG, "from CaCertUninstaller: ", e);
+ } finally {
+ keyChainConnection.close();
+ keyChainConnection = null;
+ }
+ } catch (InterruptedException ie) {
+ Log.w(TAG, "CaCertUninstaller: ", ie);
+ Thread.currentThread().interrupt();
+ }
+ }
+
void wipeDataLocked(int flags) {
// If the SD card is encrypted and non-removable, we have to force a wipe.
boolean forceExtWipe = !Environment.isExternalStorageRemovable() && isExtStorageEncrypted();
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 3f58fa4..8f4b6c2 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -8154,18 +8154,20 @@
}
@Override
- public void convertFromTranslucent(IBinder token) {
+ public boolean convertFromTranslucent(IBinder token) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (this) {
final ActivityRecord r = ActivityRecord.isInStackLocked(token);
if (r == null) {
- return;
+ return false;
}
if (r.changeWindowTranslucency(true)) {
mWindowManager.setAppFullscreen(token, true);
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0);
+ return true;
}
+ return false;
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -8173,19 +8175,21 @@
}
@Override
- public void convertToTranslucent(IBinder token) {
+ public boolean convertToTranslucent(IBinder token) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (this) {
final ActivityRecord r = ActivityRecord.isInStackLocked(token);
if (r == null) {
- return;
+ return false;
}
if (r.changeWindowTranslucency(false)) {
r.task.stack.convertToTranslucent(r);
mWindowManager.setAppFullscreen(token, false);
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0);
+ return true;
}
+ return false;
}
} finally {
Binder.restoreCallingIdentity(origId);
diff --git a/services/java/com/android/server/connectivity/PacManager.java b/services/java/com/android/server/connectivity/PacManager.java
index defe9f0..0b68ff5 100644
--- a/services/java/com/android/server/connectivity/PacManager.java
+++ b/services/java/com/android/server/connectivity/PacManager.java
@@ -1,14 +1,31 @@
+/**
+ * Copyright (c) 2013, 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.server.connectivity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.net.ConnectivityManager;
+import android.content.ServiceConnection;
import android.net.ProxyProperties;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -17,42 +34,26 @@
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.net.IProxyService;
+import com.android.server.IoThread;
+import libcore.io.Streams;
-import org.apache.http.HttpEntity;
-import org.apache.http.HttpException;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpRequest;
-import org.apache.http.HttpResponse;
-import org.apache.http.client.ClientProtocolException;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.conn.params.ConnRouteParams;
-import org.apache.http.conn.routing.HttpRoute;
-import org.apache.http.conn.routing.HttpRoutePlanner;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.protocol.HttpContext;
-
-import java.io.BufferedReader;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.InetAddress;
-import java.net.ProxySelector;
import java.net.URL;
import java.net.URLConnection;
/**
* @hide
*/
-public class PacManager implements Runnable {
- public static final int NO_ERROR = 0;
- public static final int PERMISSION_DENIED = 1;
- public static final String PROXY_SERVICE = "com.android.net.IProxyService";
+public class PacManager {
+ public static final String PROXY_PACKAGE = "com.android.pacprocessor";
+ public static final String PROXY_SERVICE = "com.android.pacprocessor.PacService";
+ public static final String PROXY_SERVICE_NAME = "com.android.net.IProxyService";
- private static final String TAG = "PACManager";
+ private static final String TAG = "PacManager";
private static final String ACTION_PAC_REFRESH = "android.net.proxy.PAC_REFRESH";
@@ -64,31 +65,57 @@
/** Keep these values up-to-date with ProxyService.java */
public static final String KEY_PROXY = "keyProxy";
private String mCurrentPac;
- private volatile String mPacUrl;
+ @GuardedBy("mProxyLock")
+ private String mPacUrl;
private AlarmManager mAlarmManager;
+ @GuardedBy("mProxyLock")
private IProxyService mProxyService;
private PendingIntent mPacRefreshIntent;
+ private ServiceConnection mConnection;
private Context mContext;
private int mCurrentDelay;
+ /**
+ * Used for locking when setting mProxyService and all references to mPacUrl or mCurrentPac.
+ */
+ private final Object mProxyLock = new Object();
+
+ private Runnable mPacDownloader = new Runnable() {
+ @Override
+ public void run() {
+ String file;
+ synchronized (mProxyLock) {
+ if (mPacUrl == null) return;
+ try {
+ file = get(mPacUrl);
+ } catch (IOException ioe) {
+ file = null;
+ Log.w(TAG, "Failed to load PAC file: " + ioe);
+ }
+ }
+ if (file != null) {
+ synchronized (mProxyLock) {
+ if (!file.equals(mCurrentPac)) {
+ setCurrentProxyScript(file);
+ }
+ }
+ longSchedule();
+ } else {
+ reschedule();
+ }
+ }
+ };
+
class PacRefreshIntentReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
- new Thread(PacManager.this).start();
+ IoThread.getHandler().post(mPacDownloader);
}
}
public PacManager(Context context) {
mContext = context;
- mProxyService = IProxyService.Stub.asInterface(
- ServiceManager.getService(PROXY_SERVICE));
- if (mProxyService == null) {
- // Added because of b10267814 where mako is restarting.
- Log.e(TAG, "PacManager: no proxy service");
- } else {
- Log.d(TAG, "PacManager: mProxyService available");
- }
mPacRefreshIntent = PendingIntent.getBroadcast(
context, 0, new Intent(ACTION_PAC_REFRESH), 0);
@@ -103,26 +130,28 @@
return mAlarmManager;
}
- public void setCurrentProxyScriptUrl(ProxyProperties proxy) {
- if (mProxyService == null) {
- Log.e(TAG, "setCurrentProxyScriptUrl: no proxy service");
- return;
- }
+ public synchronized void setCurrentProxyScriptUrl(ProxyProperties proxy) {
if (!TextUtils.isEmpty(proxy.getPacFileUrl())) {
- try {
- mProxyService.startPacSystem();
+ synchronized (mProxyLock) {
mPacUrl = proxy.getPacFileUrl();
- mCurrentDelay = DELAY_1;
- getAlarmManager().cancel(mPacRefreshIntent);
- new Thread(this).start();
- } catch (RemoteException e) {
- Log.e(TAG, "Unable to reach ProxyService - PAC will not be started", e);
}
+ mCurrentDelay = DELAY_1;
+ getAlarmManager().cancel(mPacRefreshIntent);
+ bind();
} else {
- try {
- mProxyService.stopPacSystem();
- } catch (RemoteException e) {
- e.printStackTrace();
+ getAlarmManager().cancel(mPacRefreshIntent);
+ synchronized (mProxyLock) {
+ mPacUrl = null;
+ mCurrentPac = null;
+ if (mProxyService != null) {
+ try {
+ mProxyService.stopPacSystem();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to stop PAC service", e);
+ } finally {
+ unbind();
+ }
+ }
}
}
}
@@ -132,51 +161,10 @@
*
* @throws IOException
*/
- public static String get(String urlString) throws IOException {
+ private static String get(String urlString) throws IOException {
URL url = new URL(urlString);
URLConnection urlConnection = url.openConnection(java.net.Proxy.NO_PROXY);
- BufferedReader in = new BufferedReader(new InputStreamReader(
- urlConnection.getInputStream()));
- String inputLine;
- String resp = "";
- while ((inputLine = in.readLine()) != null) {
- resp = resp + inputLine + "\n";
- }
- in.close();
- return resp;
- }
-
- private static String toString(InputStream content) throws IOException {
- StringBuffer buffer = new StringBuffer();
- String line;
- BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(content));
-
- while ((line = bufferedReader.readLine()) != null) {
- if (buffer.length() != 0) {
- buffer.append('\n');
- }
- buffer.append(line);
- }
-
- return buffer.toString();
- }
-
- @Override
- public void run() {
- String file;
- try {
- file = get(mPacUrl);
- } catch (IOException ioe) {
- file = null;
- }
- if (file != null) {
- if (!file.equals(mCurrentPac)) {
- setCurrentProxyScript(file);
- }
- longSchedule();
- } else {
- reschedule();
- }
+ return new String(Streams.readFully(urlConnection.getInputStream()));
}
private int getNextDelay(int currentDelay) {
@@ -227,14 +215,60 @@
return false;
}
try {
- if (mProxyService.setPacFile(script) != NO_ERROR) {
- Log.e(TAG, "Unable to parse proxy script.");
- return false;
- }
+ mProxyService.setPacFile(script);
mCurrentPac = script;
} catch (RemoteException e) {
Log.e(TAG, "Unable to set PAC file", e);
}
return true;
}
+
+ private void bind() {
+ if (mContext == null) {
+ Log.e(TAG, "No context for binding");
+ return;
+ }
+ Intent intent = new Intent();
+ intent.setClassName(PROXY_PACKAGE, PROXY_SERVICE);
+ mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceDisconnected(ComponentName component) {
+ synchronized (mProxyLock) {
+ mProxyService = null;
+ }
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName component, IBinder binder) {
+ synchronized (mProxyLock) {
+ try {
+ Log.d(TAG, "Adding service " + PROXY_SERVICE_NAME + " "
+ + binder.getInterfaceDescriptor());
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Remote Exception", e1);
+ }
+ ServiceManager.addService(PROXY_SERVICE_NAME, binder);
+ mProxyService = IProxyService.Stub.asInterface(binder);
+ if (mProxyService == null) {
+ Log.e(TAG, "No proxy service");
+ } else {
+ try {
+ mProxyService.startPacSystem();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to reach ProxyService - PAC will not be started", e);
+ }
+ IoThread.getHandler().post(mPacDownloader);
+ }
+ }
+ }
+ };
+ Log.e(TAG, "Attempting to bind");
+ mContext.bindService(intent, mConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE);
+ }
+
+ private void unbind() {
+ mContext.unbindService(mConnection);
+ mConnection = null;
+ }
}
diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java
index f89917c3..846a74d 100644
--- a/services/java/com/android/server/display/WifiDisplayController.java
+++ b/services/java/com/android/server/display/WifiDisplayController.java
@@ -83,11 +83,6 @@
private static final int CONNECT_MAX_RETRIES = 3;
private static final int CONNECT_RETRY_DELAY_MILLIS = 500;
- // A unique token to identify the remote submix that is managed by Wifi display.
- // It must match what the media server uses when it starts recording the submix
- // for transmission. We use 0 although the actual value is currently ignored.
- private static final int REMOTE_SUBMIX_ADDRESS = 0;
-
private final Context mContext;
private final Handler mHandler;
private final Listener mListener;
@@ -95,8 +90,6 @@
private final WifiP2pManager mWifiP2pManager;
private final Channel mWifiP2pChannel;
- private final AudioManager mAudioManager;
-
private boolean mWifiP2pEnabled;
private boolean mWfdEnabled;
private boolean mWfdEnabling;
@@ -146,9 +139,6 @@
// True if RTSP has connected.
private boolean mRemoteDisplayConnected;
- // True if the remote submix is enabled.
- private boolean mRemoteSubmixOn;
-
// The information we have most recently told WifiDisplayAdapter about.
private WifiDisplay mAdvertisedDisplay;
private Surface mAdvertisedDisplaySurface;
@@ -164,8 +154,6 @@
mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE);
mWifiP2pChannel = mWifiP2pManager.initialize(context, handler.getLooper(), null);
- mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
-
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
@@ -211,7 +199,6 @@
pw.println("mRemoteDisplay=" + mRemoteDisplay);
pw.println("mRemoteDisplayInterface=" + mRemoteDisplayInterface);
pw.println("mRemoteDisplayConnected=" + mRemoteDisplayConnected);
- pw.println("mRemoteSubmixOn=" + mRemoteSubmixOn);
pw.println("mAdvertisedDisplay=" + mAdvertisedDisplay);
pw.println("mAdvertisedDisplaySurface=" + mAdvertisedDisplaySurface);
pw.println("mAdvertisedDisplayWidth=" + mAdvertisedDisplayWidth);
@@ -482,7 +469,6 @@
mHandler.removeCallbacks(mRtspTimeout);
mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_DISABLED);
- setRemoteSubmixOn(false);
unadvertiseDisplay();
// continue to next step
@@ -626,7 +612,6 @@
return; // done
}
- setRemoteSubmixOn(true);
mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_SOURCE);
final WifiP2pDevice oldDevice = mConnectedDevice;
@@ -677,13 +662,6 @@
}
}
- private void setRemoteSubmixOn(boolean on) {
- if (mRemoteSubmixOn != on) {
- mRemoteSubmixOn = on;
- mAudioManager.setRemoteSubmixOn(on, REMOTE_SUBMIX_ADDRESS);
- }
- }
-
private void handleStateChanged(boolean enabled) {
mWifiP2pEnabled = enabled;
updateWfdEnableState();
diff --git a/services/java/com/android/server/print/RemotePrintService.java b/services/java/com/android/server/print/RemotePrintService.java
index 491dddc..5c684601 100644
--- a/services/java/com/android/server/print/RemotePrintService.java
+++ b/services/java/com/android/server/print/RemotePrintService.java
@@ -30,8 +30,6 @@
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.print.IPrinterDiscoverySessionController;
-import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrintJobInfo;
import android.print.PrintManager;
import android.print.PrinterId;
@@ -79,6 +77,10 @@
private boolean mDestroyed;
+ private boolean mAllPrintJobsHandled;
+
+ private boolean mHasPrinterDiscoverySession;
+
public RemotePrintService(Context context, ComponentName componentName, int userId,
RemotePrintSpooler spooler) {
mContext = context;
@@ -97,6 +99,8 @@
private void handleDestroy() {
throwIfDestroyed();
ensureUnbound();
+ mAllPrintJobsHandled = false;
+ mHasPrinterDiscoverySession = false;
mDestroyed = true;
}
@@ -110,18 +114,27 @@
}
private void handleBinderDied() {
+ mAllPrintJobsHandled = false;
+ mHasPrinterDiscoverySession = false;
mPendingCommands.clear();
ensureUnbound();
}
private void handleOnAllPrintJobsHandled() {
throwIfDestroyed();
+
+ mAllPrintJobsHandled = true;
+
if (isBound()) {
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserId + "] handleOnAllPrintJobsHandled()");
}
- // If bound and all the work is completed, then unbind.
- ensureUnbound();
+
+ // If the service has a printer discovery session
+ // created we should not disconnect from it just yet.
+ if (!mHasPrinterDiscoverySession) {
+ ensureUnbound();
+ }
}
}
@@ -153,6 +166,9 @@
private void handleOnPrintJobQueued(final PrintJobInfo printJob) {
throwIfDestroyed();
+
+ mAllPrintJobsHandled = false;
+
if (!isBound()) {
ensureBound();
mPendingCommands.add(new Runnable() {
@@ -173,20 +189,18 @@
}
}
- public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
- mHandler.obtainMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION,
- observer).sendToTarget();
+ public void createPrinterDiscoverySession() {
+ mHandler.sendEmptyMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION);
}
- private void handleCreatePrinterDiscoverySession(
- final IPrinterDiscoverySessionObserver observer) {
+ private void handleCreatePrinterDiscoverySession() {
throwIfDestroyed();
if (!isBound()) {
ensureBound();
mPendingCommands.add(new Runnable() {
@Override
public void run() {
- handleCreatePrinterDiscoverySession(observer);
+ handleCreatePrinterDiscoverySession();
}
});
} else {
@@ -194,9 +208,126 @@
Slog.i(LOG_TAG, "[user: " + mUserId + "] createPrinterDiscoverySession()");
}
try {
- mPrintService.createPrinterDiscoverySession(observer);
+ mPrintService.createPrinterDiscoverySession();
} catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error announcing start printer dicovery.", re);
+ Slog.e(LOG_TAG, "Error creating printer dicovery session.", re);
+ }
+
+ mHasPrinterDiscoverySession = true;
+ }
+ }
+
+ public void destroyPrinterDiscoverySession() {
+ mHandler.sendEmptyMessage(MyHandler.MSG_DESTROY_PRINTER_DISCOVERY_SESSION);
+ }
+
+ private void handleDestroyPrinterDiscoverySession() {
+ throwIfDestroyed();
+ if (!isBound()) {
+ ensureBound();
+ mPendingCommands.add(new Runnable() {
+ @Override
+ public void run() {
+ handleDestroyPrinterDiscoverySession();
+ }
+ });
+ } else {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserId + "] destroyPrinterDiscoverySession()");
+ }
+
+ mHasPrinterDiscoverySession = false;
+
+ try {
+ mPrintService.destroyPrinterDiscoverySession();
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error destroying printer dicovery session.", re);
+ }
+
+ // If the service has no print jobs and no active discovery
+ // session anymore we should disconnect from it.
+ if (mAllPrintJobsHandled) {
+ ensureUnbound();
+ }
+ }
+ }
+
+ public void startPrinterDiscovery(List<PrinterId> priorityList) {
+ mHandler.obtainMessage(MyHandler.MSG_START_PRINTER_DISCOVERY,
+ priorityList).sendToTarget();
+ }
+
+ private void handleStartPrinterDiscovery(final List<PrinterId> priorityList) {
+ throwIfDestroyed();
+ if (!isBound()) {
+ ensureBound();
+ mPendingCommands.add(new Runnable() {
+ @Override
+ public void run() {
+ handleStartPrinterDiscovery(priorityList);
+ }
+ });
+ } else {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserId + "] startPrinterDiscovery()");
+ }
+ try {
+ mPrintService.startPrinterDiscovery(priorityList);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error starting printer dicovery.", re);
+ }
+ }
+ }
+
+ public void stopPrinterDiscovery() {
+ mHandler.sendEmptyMessage(MyHandler.MSG_STOP_PRINTER_DISCOVERY);
+ }
+
+ private void handleStopPrinterDiscovery() {
+ throwIfDestroyed();
+ if (!isBound()) {
+ ensureBound();
+ mPendingCommands.add(new Runnable() {
+ @Override
+ public void run() {
+ handleStopPrinterDiscovery();
+ }
+ });
+ } else {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserId + "] stopPrinterDiscovery()");
+ }
+ try {
+ mPrintService.stopPrinterDiscovery();
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error stopping printer dicovery.", re);
+ }
+ }
+ }
+
+ public void requestPrinterUpdate(PrinterId printerId) {
+ mHandler.obtainMessage(MyHandler.MSG_REQUEST_PRINTER_UPDATE,
+ printerId).sendToTarget();
+ }
+
+ private void handleRequestPrinterUpdate(final PrinterId printerId) {
+ throwIfDestroyed();
+ if (!isBound()) {
+ ensureBound();
+ mPendingCommands.add(new Runnable() {
+ @Override
+ public void run() {
+ handleRequestPrinterUpdate(printerId);
+ }
+ });
+ } else {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserId + "] requestPrinterUpdate()");
+ }
+ try {
+ mPrintService.requestPrinterUpdate(printerId);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error requesting a printer update.", re);
}
}
}
@@ -279,20 +410,47 @@
}
private final class MyHandler extends Handler {
- public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 1;
- public static final int MSG_ON_REQUEST_CANCEL_PRINT_JOB = 2;
- public static final int MSG_ON_PRINT_JOB_QUEUED = 3;
- public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 4;
- public static final int MSG_DESTROY = 6;
- public static final int MSG_BINDER_DIED = 7;
+ public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 1;
+ public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2;
+ public static final int MSG_START_PRINTER_DISCOVERY = 3;
+ public static final int MSG_STOP_PRINTER_DISCOVERY = 4;
+ public static final int MSG_REQUEST_PRINTER_UPDATE = 5;
+ public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 6;
+ public static final int MSG_ON_REQUEST_CANCEL_PRINT_JOB = 7;
+ public static final int MSG_ON_PRINT_JOB_QUEUED = 8;
+ public static final int MSG_DESTROY = 9;
+ public static final int MSG_BINDER_DIED = 10;
public MyHandler(Looper looper) {
super(looper, null, false);
}
@Override
+ @SuppressWarnings("unchecked")
public void handleMessage(Message message) {
switch (message.what) {
+ case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
+ handleCreatePrinterDiscoverySession();
+ } break;
+
+ case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: {
+ handleDestroyPrinterDiscoverySession();
+ } break;
+
+ case MSG_START_PRINTER_DISCOVERY: {
+ List<PrinterId> priorityList = (ArrayList<PrinterId>) message.obj;
+ handleStartPrinterDiscovery(priorityList);
+ } break;
+
+ case MSG_STOP_PRINTER_DISCOVERY: {
+ handleStopPrinterDiscovery();
+ } break;
+
+ case MSG_REQUEST_PRINTER_UPDATE: {
+ PrinterId printerId = (PrinterId) message.obj;
+ handleRequestPrinterUpdate(printerId);
+ } break;
+
case MSG_ON_ALL_PRINT_JOBS_HANDLED: {
handleOnAllPrintJobsHandled();
} break;
@@ -307,13 +465,6 @@
handleOnPrintJobQueued(printJob);
} break;
- case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
- IPrinterDiscoverySessionObserver observer =
- (IPrinterDiscoverySessionObserver) message.obj;
- handleCreatePrinterDiscoverySession(new SecurePrinterDiscoverySessionObserver(
- mComponentName, observer));
- } break;
-
case MSG_DESTROY: {
handleDestroy();
} break;
@@ -363,7 +514,7 @@
}
@Override
- public boolean setPrintJobState(int printJobId, int state, CharSequence error) {
+ public boolean setPrintJobState(int printJobId, int state, String error) {
RemotePrintService service = mWeakService.get();
if (service != null) {
final long identity = Binder.clearCallingIdentity();
@@ -402,79 +553,70 @@
}
}
}
- }
-
- private static final class SecurePrinterDiscoverySessionObserver
- extends IPrinterDiscoverySessionObserver.Stub {
- private final ComponentName mComponentName;
-
- private final IPrinterDiscoverySessionObserver mDecoratedObsever;
-
- public SecurePrinterDiscoverySessionObserver(ComponentName componentName,
- IPrinterDiscoverySessionObserver observer) {
- mComponentName = componentName;
- mDecoratedObsever = observer;
- }
@Override
public void onPrintersAdded(List<PrinterInfo> printers) {
- throwIfPrinterIdsForPrinterInfoTampered(printers);
- try {
- mDecoratedObsever.onPrintersAdded(printers);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error delegating to onPrintersAdded", re);
- }
- }
-
- @Override
- public void onPrintersUpdated(List<PrinterInfo> printers) {
- throwIfPrinterIdsForPrinterInfoTampered(printers);
- try {
- mDecoratedObsever.onPrintersUpdated(printers);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error delegating to onPrintersUpdated.", re);
+ RemotePrintService service = mWeakService.get();
+ if (service != null) {
+ throwIfPrinterIdsForPrinterInfoTampered(service.mComponentName, printers);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ service.mSpooler.onPrintersAdded(printers);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
@Override
public void onPrintersRemoved(List<PrinterId> printerIds) {
- throwIfPrinterIdsTampered(printerIds);
- try {
- mDecoratedObsever.onPrintersRemoved(printerIds);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error delegating to onPrintersRemoved", re);
+ RemotePrintService service = mWeakService.get();
+ if (service != null) {
+ throwIfPrinterIdsTampered(service.mComponentName, printerIds);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ service.mSpooler.onPrintersRemoved(printerIds);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
@Override
- public void setController(IPrinterDiscoverySessionController controller) {
- try {
- mDecoratedObsever.setController(controller);
- } catch (RemoteException re) {
- Slog.e(LOG_TAG, "Error setting controller", re);
+ public void onPrintersUpdated(List<PrinterInfo> printers) {
+ RemotePrintService service = mWeakService.get();
+ if (service != null) {
+ throwIfPrinterIdsForPrinterInfoTampered(service.mComponentName, printers);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ service.mSpooler.onPrintersUpdated(printers);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
- private void throwIfPrinterIdsForPrinterInfoTampered(
+ private void throwIfPrinterIdsForPrinterInfoTampered(ComponentName serviceName,
List<PrinterInfo> printerInfos) {
final int printerInfoCount = printerInfos.size();
for (int i = 0; i < printerInfoCount; i++) {
PrinterId printerId = printerInfos.get(i).getId();
- throwIfPrinterIdTampered(printerId);
+ throwIfPrinterIdTampered(serviceName, printerId);
}
}
- private void throwIfPrinterIdsTampered(List<PrinterId> printerIds) {
+ private void throwIfPrinterIdsTampered(ComponentName serviceName,
+ List<PrinterId> printerIds) {
final int printerIdCount = printerIds.size();
for (int i = 0; i < printerIdCount; i++) {
PrinterId printerId = printerIds.get(i);
- throwIfPrinterIdTampered(printerId);
+ throwIfPrinterIdTampered(serviceName, printerId);
}
}
- private void throwIfPrinterIdTampered(PrinterId printerId) {
+ private void throwIfPrinterIdTampered(ComponentName serviceName, PrinterId printerId) {
if (printerId == null || printerId.getServiceName() == null
- || !printerId.getServiceName().equals(mComponentName)) {
+ || !printerId.getServiceName().equals(serviceName)) {
throw new IllegalArgumentException("Invalid printer id: " + printerId);
}
}
diff --git a/services/java/com/android/server/print/RemotePrintSpooler.java b/services/java/com/android/server/print/RemotePrintSpooler.java
index c932e9b..d261288 100644
--- a/services/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/java/com/android/server/print/RemotePrintSpooler.java
@@ -32,9 +32,10 @@
import android.print.IPrintSpooler;
import android.print.IPrintSpoolerCallbacks;
import android.print.IPrintSpoolerClient;
-import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrintAttributes;
import android.print.PrintJobInfo;
+import android.print.PrinterId;
+import android.print.PrinterInfo;
import android.util.Slog;
import android.util.TimedRemoteCaller;
@@ -92,7 +93,11 @@
public static interface PrintSpoolerCallbacks {
public void onPrintJobQueued(PrintJobInfo printJob);
public void onAllPrintJobsForServiceHandled(ComponentName printService);
- public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer);
+ public void createPrinterDiscoverySession();
+ public void destroyPrinterDiscoverySession();
+ public void startPrinterDiscovery(List<PrinterId> priorityList);
+ public void stopPrinterDiscovery();
+ public void requestPrinterUpdate(PrinterId printerId);
}
public RemotePrintSpooler(Context context, int userId,
@@ -209,7 +214,7 @@
return null;
}
- public final boolean setPrintJobState(int printJobId, int state, CharSequence error) {
+ public final boolean setPrintJobState(int printJobId, int state, String error) {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
@@ -300,6 +305,78 @@
}
}
+ public final void onPrintersAdded(List<PrinterInfo> printers) {
+ throwIfCalledOnMainThread();
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ mCanUnbind = false;
+ }
+ try {
+ getRemoteInstanceLazy().onPrintersAdded(printers);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error adding printers.", re);
+ } catch (TimeoutException te) {
+ Slog.e(LOG_TAG, "Error adding printers.", te);
+ } finally {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
+ + "] onPrintersAdded()");
+ }
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ public final void onPrintersRemoved(List<PrinterId> printerIds) {
+ throwIfCalledOnMainThread();
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ mCanUnbind = false;
+ }
+ try {
+ getRemoteInstanceLazy().onPrintersRemoved(printerIds);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error removing printers.", re);
+ } catch (TimeoutException te) {
+ Slog.e(LOG_TAG, "Error removing printers.", te);
+ } finally {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
+ + "] onPrintersRemoved()");
+ }
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ public final void onPrintersUpdated(List<PrinterInfo> printers) {
+ throwIfCalledOnMainThread();
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ mCanUnbind = false;
+ }
+ try {
+ getRemoteInstanceLazy().onPrintersUpdated(printers);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error updating printers.", re);
+ } catch (TimeoutException te) {
+ Slog.e(LOG_TAG, "Error updating printers.", te);
+ } finally {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
+ + "] onPrintersUpdted()");
+ }
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
+ }
+ }
+
private IPrintSpooler getRemoteInstanceLazy() throws TimeoutException {
synchronized (mLock) {
if (mRemoteInstance != null) {
@@ -488,7 +565,7 @@
}
public boolean setPrintJobState(IPrintSpooler target, int printJobId,
- int status, CharSequence error) throws RemoteException, TimeoutException {
+ int status, String error) throws RemoteException, TimeoutException {
final int sequence = onBeforeRemoteCall();
target.setPrintJobState(printJobId, status, error, mCallback, sequence);
return getResultTimed(sequence);
@@ -597,12 +674,64 @@
}
@Override
- public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
+ public void createPrinterDiscoverySession() {
RemotePrintSpooler spooler = mWeakSpooler.get();
if (spooler != null) {
final long identity = Binder.clearCallingIdentity();
try {
- spooler.mCallbacks.createPrinterDiscoverySession(observer);
+ spooler.mCallbacks.createPrinterDiscoverySession();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void destroyPrinterDiscoverySession() {
+ RemotePrintSpooler spooler = mWeakSpooler.get();
+ if (spooler != null) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ spooler.mCallbacks.destroyPrinterDiscoverySession();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void startPrinterDiscovery(List<PrinterId> priorityList) {
+ RemotePrintSpooler spooler = mWeakSpooler.get();
+ if (spooler != null) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ spooler.mCallbacks.startPrinterDiscovery(priorityList);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void stopPrinterDiscovery() {
+ RemotePrintSpooler spooler = mWeakSpooler.get();
+ if (spooler != null) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ spooler.mCallbacks.stopPrinterDiscovery();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
+ public void requestPrinterUpdate(PrinterId printerId) {
+ RemotePrintSpooler spooler = mWeakSpooler.get();
+ if (spooler != null) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ spooler.mCallbacks.requestPrinterUpdate(printerId);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/java/com/android/server/print/UserState.java b/services/java/com/android/server/print/UserState.java
index ffcc9c3..9d7cfdd 100644
--- a/services/java/com/android/server/print/UserState.java
+++ b/services/java/com/android/server/print/UserState.java
@@ -21,8 +21,8 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.print.IPrinterDiscoverySessionObserver;
import android.print.PrintJobInfo;
+import android.print.PrinterId;
import android.printservice.PrintServiceInfo;
import android.provider.Settings;
import android.text.TextUtils;
@@ -105,7 +105,7 @@
}
@Override
- public void createPrinterDiscoverySession(IPrinterDiscoverySessionObserver observer) {
+ public void createPrinterDiscoverySession() {
final List<RemotePrintService> services;
synchronized (mLock) {
throwIfDestroyedLocked();
@@ -117,7 +117,73 @@
final int serviceCount = services.size();
for (int i = 0; i < serviceCount; i++) {
RemotePrintService service = services.get(i);
- service.createPrinterDiscoverySession(observer);
+ service.createPrinterDiscoverySession();
+ }
+ }
+
+ @Override
+ public void destroyPrinterDiscoverySession() {
+ final List<RemotePrintService> services;
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ if (mActiveServices.isEmpty()) {
+ return;
+ }
+ services = new ArrayList<RemotePrintService>(mActiveServices.values());
+ }
+ final int serviceCount = services.size();
+ for (int i = 0; i < serviceCount; i++) {
+ RemotePrintService service = services.get(i);
+ service.destroyPrinterDiscoverySession();
+ }
+ }
+
+ @Override
+ public void startPrinterDiscovery(List<PrinterId> printerIds) {
+ final List<RemotePrintService> services;
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ if (mActiveServices.isEmpty()) {
+ return;
+ }
+ services = new ArrayList<RemotePrintService>(mActiveServices.values());
+ }
+ final int serviceCount = services.size();
+ for (int i = 0; i < serviceCount; i++) {
+ RemotePrintService service = services.get(i);
+ service.startPrinterDiscovery(printerIds);
+ }
+ }
+
+ @Override
+ public void stopPrinterDiscovery() {
+ final List<RemotePrintService> services;
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ if (mActiveServices.isEmpty()) {
+ return;
+ }
+ services = new ArrayList<RemotePrintService>(mActiveServices.values());
+ }
+ final int serviceCount = services.size();
+ for (int i = 0; i < serviceCount; i++) {
+ RemotePrintService service = services.get(i);
+ service.stopPrinterDiscovery();
+ }
+ }
+
+ @Override
+ public void requestPrinterUpdate(PrinterId printerId) {
+ final RemotePrintService service;
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ if (mActiveServices.isEmpty()) {
+ return;
+ }
+ service = mActiveServices.get(printerId.getServiceName());
+ }
+ if (service != null) {
+ service.requestPrinterUpdate(printerId);
}
}