summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/app/ActivityManagerNative.java8
-rw-r--r--core/java/android/app/ApplicationPackageManager.java27
-rw-r--r--core/java/android/app/ContextImpl.java12
-rw-r--r--core/java/android/app/IActivityManager.java3
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java52
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl7
-rw-r--r--core/java/android/app/backup/BackupTransport.java334
-rw-r--r--core/java/android/bluetooth/BluetoothProfile.java6
-rw-r--r--core/java/android/content/Context.java9
-rw-r--r--core/java/android/content/IRestrictionsManager.aidl30
-rw-r--r--core/java/android/content/RestrictionEntry.java157
-rw-r--r--core/java/android/content/RestrictionsManager.java344
-rw-r--r--core/java/android/content/SharedPreferences.java9
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl8
-rw-r--r--core/java/android/content/pm/PackageInstaller.java16
-rw-r--r--core/java/android/content/pm/PackageManager.java40
-rw-r--r--core/java/android/content/pm/PackageParser.java11
-rw-r--r--core/java/android/hardware/Sensor.java409
-rw-r--r--core/java/android/hardware/SensorEventListener.java12
-rw-r--r--core/java/android/hardware/SensorManager.java12
-rw-r--r--core/java/android/hardware/SystemSensorManager.java25
-rw-r--r--core/java/android/hardware/camera2/CameraAccessException.java5
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java17
-rw-r--r--core/java/android/os/FileBridge.java165
-rw-r--r--core/java/android/os/Trace.java2
-rw-r--r--core/java/android/os/Vibrator.java2
-rw-r--r--core/java/android/provider/ContactsContract.java25
-rw-r--r--core/java/android/provider/Settings.java6
-rw-r--r--core/java/android/speech/tts/Markup.java537
-rw-r--r--core/java/android/speech/tts/SynthesisRequestV2.java38
-rw-r--r--core/java/android/speech/tts/TextToSpeechClient.java67
-rw-r--r--core/java/android/speech/tts/TextToSpeechService.java53
-rw-r--r--core/java/android/speech/tts/Utterance.java595
-rw-r--r--core/java/android/view/SurfaceControl.java34
-rw-r--r--core/java/android/view/View.java30
-rw-r--r--core/java/android/view/ViewRootImpl.java63
-rw-r--r--core/java/android/view/textservice/SpellCheckerSession.java8
-rw-r--r--core/java/android/widget/DatePicker.java4
-rw-r--r--core/java/android/widget/GridLayout.java228
39 files changed, 3164 insertions, 246 deletions
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 0f65454..56462ae 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -943,7 +943,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
b = data.readStrongBinder();
IUiAutomationConnection c = IUiAutomationConnection.Stub.asInterface(b);
int userId = data.readInt();
- boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId);
+ String abiOverride = data.readString();
+ boolean res = startInstrumentation(className, profileFile, fl, arguments, w, c, userId,
+ abiOverride);
reply.writeNoException();
reply.writeInt(res ? 1 : 0);
return true;
@@ -3339,7 +3341,8 @@ class ActivityManagerProxy implements IActivityManager
public boolean startInstrumentation(ComponentName className, String profileFile,
int flags, Bundle arguments, IInstrumentationWatcher watcher,
- IUiAutomationConnection connection, int userId) throws RemoteException {
+ IUiAutomationConnection connection, int userId, String instructionSet)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -3350,6 +3353,7 @@ class ActivityManagerProxy implements IActivityManager
data.writeStrongBinder(watcher != null ? watcher.asBinder() : null);
data.writeStrongBinder(connection != null ? connection.asBinder() : null);
data.writeInt(userId);
+ data.writeString(instructionSet);
mRemote.transact(START_INSTRUMENTATION_TRANSACTION, data, reply, 0);
reply.readException();
boolean res = reply.readInt() != 0;
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 2f35160..84673d9 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1455,10 +1455,10 @@ final class ApplicationPackageManager extends PackageManager {
* @hide
*/
@Override
- public void addForwardingIntentFilter(IntentFilter filter, boolean removable, int userIdOrig,
- int userIdDest) {
+ public void addCrossProfileIntentFilter(IntentFilter filter, boolean removable,
+ int sourceUserId, int targetUserId) {
try {
- mPM.addForwardingIntentFilter(filter, removable, userIdOrig, userIdDest);
+ mPM.addCrossProfileIntentFilter(filter, removable, sourceUserId, targetUserId);
} catch (RemoteException e) {
// Should never happen!
}
@@ -1468,14 +1468,31 @@ final class ApplicationPackageManager extends PackageManager {
* @hide
*/
@Override
- public void clearForwardingIntentFilters(int userIdOrig) {
+ public void addForwardingIntentFilter(IntentFilter filter, boolean removable, int sourceUserId,
+ int targetUserId) {
+ addCrossProfileIntentFilter(filter, removable, sourceUserId, targetUserId);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void clearCrossProfileIntentFilters(int sourceUserId) {
try {
- mPM.clearForwardingIntentFilters(userIdOrig);
+ mPM.clearCrossProfileIntentFilters(sourceUserId);
} catch (RemoteException e) {
// Should never happen!
}
}
+ /**
+ * @hide
+ */
+ @Override
+ public void clearForwardingIntentFilters(int sourceUserId) {
+ clearCrossProfileIntentFilters(sourceUserId);
+ }
+
private final ContextImpl mContext;
private final IPackageManager mPM;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index e66534b..4f335bb 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -35,7 +35,9 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.IIntentReceiver;
import android.content.IntentSender;
+import android.content.IRestrictionsManager;
import android.content.ReceiverCallNotAllowedException;
+import android.content.RestrictionsManager;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
@@ -662,6 +664,13 @@ class ContextImpl extends Context {
}
});
+ registerService(RESTRICTIONS_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(RESTRICTIONS_SERVICE);
+ IRestrictionsManager service = IRestrictionsManager.Stub.asInterface(b);
+ return new RestrictionsManager(ctx, service);
+ }
+ });
registerService(PRINT_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder iBinder = ServiceManager.getService(Context.PRINT_SERVICE);
@@ -1755,7 +1764,8 @@ class ContextImpl extends Context {
arguments.setAllowFds(false);
}
return ActivityManagerNative.getDefault().startInstrumentation(
- className, profileFile, 0, arguments, null, null, getUserId());
+ className, profileFile, 0, arguments, null, null, getUserId(),
+ null /* ABI override */);
} catch (RemoteException e) {
// System has crashed, nothing we can do.
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 8434c2a..bf2d7e5 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -176,7 +176,8 @@ public interface IActivityManager extends IInterface {
public boolean startInstrumentation(ComponentName className, String profileFile,
int flags, Bundle arguments, IInstrumentationWatcher watcher,
- IUiAutomationConnection connection, int userId) throws RemoteException;
+ IUiAutomationConnection connection, int userId,
+ String abiOverride) throws RemoteException;
public void finishInstrumentation(IApplicationThread target,
int resultCode, Bundle results) throws RemoteException;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 77b1acf..b3b1d47 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -25,6 +25,7 @@ import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.RestrictionsManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Process;
@@ -176,15 +177,16 @@ public class DevicePolicyManager {
public static final String ACTION_SET_NEW_PASSWORD
= "android.app.action.SET_NEW_PASSWORD";
/**
- * Flag for {@link #addForwardingIntentFilter}: the intents will forwarded to the primary user.
+ * Flag used by {@link #addCrossProfileIntentFilter} to allow access of certain intents from a
+ * managed profile to its parent.
*/
- public static int FLAG_TO_PRIMARY_USER = 0x0001;
+ public static int FLAG_PARENT_CAN_ACCESS_MANAGED = 0x0001;
/**
- * Flag for {@link #addForwardingIntentFilter}: the intents will be forwarded to the managed
- * profile.
+ * Flag used by {@link #addCrossProfileIntentFilter} to allow access of certain intents from the
+ * parent to its managed profile.
*/
- public static int FLAG_TO_MANAGED_PROFILE = 0x0002;
+ public static int FLAG_MANAGED_CAN_ACCESS_PARENT = 0x0002;
/**
* Return true if the given administrator component is currently
@@ -1951,17 +1953,16 @@ public class DevicePolicyManager {
}
/**
- * Called by a profile owner to forward intents sent from the managed profile to the owner, or
- * from the owner to the managed profile.
- * If an intent matches this intent filter, then activities belonging to the other user can
- * respond to this intent.
+ * Called by the profile owner so that some intents sent in the managed profile can also be
+ * resolved in the parent, or vice versa.
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
- * @param filter if an intent matches this IntentFilter, then it can be forwarded.
+ * @param filter The {@link IntentFilter} the intent has to match to be also resolved in the
+ * other profile
*/
- public void addForwardingIntentFilter(ComponentName admin, IntentFilter filter, int flags) {
+ public void addCrossProfileIntentFilter(ComponentName admin, IntentFilter filter, int flags) {
if (mService != null) {
try {
- mService.addForwardingIntentFilter(admin, filter, flags);
+ mService.addCrossProfileIntentFilter(admin, filter, flags);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1969,14 +1970,14 @@ public class DevicePolicyManager {
}
/**
- * Called by a profile owner to remove the forwarding intent filters from the current user
- * and from the owner.
+ * Called by a profile owner to remove the cross-profile intent filters from the managed profile
+ * and from the parent.
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
*/
- public void clearForwardingIntentFilters(ComponentName admin) {
+ public void clearCrossProfileIntentFilters(ComponentName admin) {
if (mService != null) {
try {
- mService.clearForwardingIntentFilters(admin);
+ mService.clearCrossProfileIntentFilters(admin);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -2320,4 +2321,23 @@ public class DevicePolicyManager {
}
}
+ /**
+ * Designates a specific broadcast receiver component as the provider for
+ * making permission requests of a local or remote administrator of the user.
+ * <p/>
+ * Only a profile owner can designate the restrictions provider.
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param receiver The component name of a BroadcastReceiver that handles the
+ * {@link RestrictionsManager#ACTION_REQUEST_PERMISSION} intent. If this param is null,
+ * it removes the restrictions provider previously assigned.
+ */
+ public void setRestrictionsProvider(ComponentName admin, ComponentName receiver) {
+ if (mService != null) {
+ try {
+ mService.setRestrictionsProvider(admin, receiver);
+ } catch (RemoteException re) {
+ Log.w(TAG, "Failed to set permission provider on device policy service");
+ }
+ }
+ }
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 3c08c14..7f754dc 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -121,9 +121,12 @@ interface IDevicePolicyManager {
void setApplicationRestrictions(in ComponentName who, in String packageName, in Bundle settings);
Bundle getApplicationRestrictions(in ComponentName who, in String packageName);
+ void setRestrictionsProvider(in ComponentName who, in ComponentName provider);
+ ComponentName getRestrictionsProvider(int userHandle);
+
void setUserRestriction(in ComponentName who, in String key, boolean enable);
- void addForwardingIntentFilter(in ComponentName admin, in IntentFilter filter, int flags);
- void clearForwardingIntentFilters(in ComponentName admin);
+ void addCrossProfileIntentFilter(in ComponentName admin, in IntentFilter filter, int flags);
+ void clearCrossProfileIntentFilters(in ComponentName admin);
boolean setApplicationBlocked(in ComponentName admin, in String packageName, boolean blocked);
int setApplicationsBlocked(in ComponentName admin, in Intent intent, boolean blocked);
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
new file mode 100644
index 0000000..da5cb10
--- /dev/null
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2014 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.app.backup;
+
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+
+import com.android.internal.backup.BackupConstants;
+import com.android.internal.backup.IBackupTransport;
+
+/**
+ * Concrete class that provides a stable-API bridge between IBackupTransport
+ * and its implementations.
+ *
+ * @hide
+ */
+public class BackupTransport {
+ IBackupTransport mBinderImpl = new TransportImpl();
+ /** @hide */
+ public IBinder getBinder() {
+ return mBinderImpl.asBinder();
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Transport self-description and general configuration interfaces
+ //
+
+ /**
+ * Ask the transport for the name under which it should be registered. This will
+ * typically be its host service's component name, but need not be.
+ */
+ public String name() {
+ throw new UnsupportedOperationException("Transport name() not implemented");
+ }
+
+ /**
+ * Ask the transport for an Intent that can be used to launch any internal
+ * configuration Activity that it wishes to present. For example, the transport
+ * may offer a UI for allowing the user to supply login credentials for the
+ * transport's off-device backend.
+ *
+ * If the transport does not supply any user-facing configuration UI, it should
+ * return null from this method.
+ *
+ * @return An Intent that can be passed to Context.startActivity() in order to
+ * launch the transport's configuration UI. This method will return null
+ * if the transport does not offer any user-facing configuration UI.
+ */
+ public Intent configurationIntent() {
+ return null;
+ }
+
+ /**
+ * On demand, supply a one-line string that can be shown to the user that
+ * describes the current backend destination. For example, a transport that
+ * can potentially associate backup data with arbitrary user accounts should
+ * include the name of the currently-active account here.
+ *
+ * @return A string describing the destination to which the transport is currently
+ * sending data. This method should not return null.
+ */
+ public String currentDestinationString() {
+ throw new UnsupportedOperationException(
+ "Transport currentDestinationString() not implemented");
+ }
+
+ /**
+ * Ask the transport where, on local device storage, to keep backup state blobs.
+ * This is per-transport so that mock transports used for testing can coexist with
+ * "live" backup services without interfering with the live bookkeeping. The
+ * returned string should be a name that is expected to be unambiguous among all
+ * available backup transports; the name of the class implementing the transport
+ * is a good choice.
+ *
+ * @return A unique name, suitable for use as a file or directory name, that the
+ * Backup Manager could use to disambiguate state files associated with
+ * different backup transports.
+ */
+ public String transportDirName() {
+ throw new UnsupportedOperationException(
+ "Transport transportDirName() not implemented");
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Key/value incremental backup support interfaces
+
+ /**
+ * Verify that this is a suitable time for a backup pass. This should return zero
+ * if a backup is reasonable right now, some positive value otherwise. This method
+ * will be called outside of the {@link #performBackup}/{@link #finishBackup} pair.
+ *
+ * <p>If this is not a suitable time for a backup, the transport should return a
+ * backoff delay, in milliseconds, after which the Backup Manager should try again.
+ *
+ * @return Zero if this is a suitable time for a backup pass, or a positive time delay
+ * in milliseconds to suggest deferring the backup pass for a while.
+ */
+ public long requestBackupTime() {
+ return 0;
+ }
+
+ /**
+ * Initialize the server side storage for this device, erasing all stored data.
+ * The transport may send the request immediately, or may buffer it. After
+ * this is called, {@link #finishBackup} will be called to ensure the request
+ * is sent and received successfully.
+ *
+ * @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far) or
+ * {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure).
+ */
+ public int initializeDevice() {
+ return BackupConstants.TRANSPORT_ERROR;
+ }
+
+ /**
+ * Send one application's key/value data update to the backup destination. The
+ * transport may send the data immediately, or may buffer it. After this is called,
+ * {@link #finishBackup} will be called to ensure the data is sent and recorded successfully.
+ *
+ * @param packageInfo The identity of the application whose data is being backed up.
+ * This specifically includes the signature list for the package.
+ * @param data The data stream that resulted from invoking the application's
+ * BackupService.doBackup() method. This may be a pipe rather than a file on
+ * persistent media, so it may not be seekable.
+ * @param wipeAllFirst When true, <i>all</i> backed-up data for the current device/account
+ * must be erased prior to the storage of the data provided here. The purpose of this
+ * is to provide a guarantee that no stale data exists in the restore set when the
+ * device begins providing incremental backups.
+ * @return one of {@link BackupConstants#TRANSPORT_OK} (OK so far),
+ * {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure), or
+ * {@link BackupConstants#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has
+ * become lost due to inactivity purge or some other reason and needs re-initializing)
+ */
+ public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd) {
+ return BackupConstants.TRANSPORT_ERROR;
+ }
+
+ /**
+ * Erase the give application's data from the backup destination. This clears
+ * out the given package's data from the current backup set, making it as though
+ * the app had never yet been backed up. After this is called, {@link finishBackup}
+ * must be called to ensure that the operation is recorded successfully.
+ *
+ * @return the same error codes as {@link #performBackup}.
+ */
+ public int clearBackupData(PackageInfo packageInfo) {
+ return BackupConstants.TRANSPORT_ERROR;
+ }
+
+ /**
+ * Finish sending application data to the backup destination. This must be
+ * called after {@link #performBackup} or {@link clearBackupData} to ensure that
+ * all data is sent. Only when this method returns true can a backup be assumed
+ * to have succeeded.
+ *
+ * @return the same error codes as {@link #performBackup}.
+ */
+ public int finishBackup() {
+ return BackupConstants.TRANSPORT_ERROR;
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Key/value dataset restore interfaces
+
+ /**
+ * Get the set of all backups currently available over this transport.
+ *
+ * @return Descriptions of the set of restore images available for this device,
+ * or null if an error occurred (the attempt should be rescheduled).
+ **/
+ public RestoreSet[] getAvailableRestoreSets() {
+ return null;
+ }
+
+ /**
+ * Get the identifying token of the backup set currently being stored from
+ * this device. This is used in the case of applications wishing to restore
+ * their last-known-good data.
+ *
+ * @return A token that can be passed to {@link #startRestore}, or 0 if there
+ * is no backup set available corresponding to the current device state.
+ */
+ public long getCurrentRestoreSet() {
+ return 0;
+ }
+
+ /**
+ * Start restoring application data from backup. After calling this function,
+ * alternate calls to {@link #nextRestorePackage} and {@link #nextRestoreData}
+ * to walk through the actual application data.
+ *
+ * @param token A backup token as returned by {@link #getAvailableRestoreSets}
+ * or {@link #getCurrentRestoreSet}.
+ * @param packages List of applications to restore (if data is available).
+ * Application data will be restored in the order given.
+ * @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far, call
+ * {@link #nextRestorePackage}) or {@link BackupConstants#TRANSPORT_ERROR}
+ * (an error occurred, the restore should be aborted and rescheduled).
+ */
+ public int startRestore(long token, PackageInfo[] packages) {
+ return BackupConstants.TRANSPORT_ERROR;
+ }
+
+ /**
+ * Get the package name of the next application with data in the backup store.
+ *
+ * @return The name of one of the packages supplied to {@link #startRestore},
+ * or "" (the empty string) if no more backup data is available,
+ * or null if an error occurred (the restore should be aborted and rescheduled).
+ */
+ public String nextRestorePackage() {
+ return null;
+ }
+
+ /**
+ * Get the data for the application returned by {@link #nextRestorePackage}.
+ * @param data An open, writable file into which the backup data should be stored.
+ * @return the same error codes as {@link #startRestore}.
+ */
+ public int getRestoreData(ParcelFileDescriptor outFd) {
+ return BackupConstants.TRANSPORT_ERROR;
+ }
+
+ /**
+ * End a restore session (aborting any in-process data transfer as necessary),
+ * freeing any resources and connections used during the restore process.
+ */
+ public void finishRestore() {
+ throw new UnsupportedOperationException(
+ "Transport finishRestore() not implemented");
+ }
+
+ /**
+ * Bridge between the actual IBackupTransport implementation and the stable API. If the
+ * binder interface needs to change, we use this layer to translate so that we can
+ * (if appropriate) decouple those framework-side changes from the BackupTransport
+ * implementations.
+ */
+ class TransportImpl extends IBackupTransport.Stub {
+
+ @Override
+ public String name() throws RemoteException {
+ return BackupTransport.this.name();
+ }
+
+ @Override
+ public Intent configurationIntent() throws RemoteException {
+ return BackupTransport.this.configurationIntent();
+ }
+
+ @Override
+ public String currentDestinationString() throws RemoteException {
+ return BackupTransport.this.currentDestinationString();
+ }
+
+ @Override
+ public String transportDirName() throws RemoteException {
+ return BackupTransport.this.transportDirName();
+ }
+
+ @Override
+ public long requestBackupTime() throws RemoteException {
+ return BackupTransport.this.requestBackupTime();
+ }
+
+ @Override
+ public int initializeDevice() throws RemoteException {
+ return BackupTransport.this.initializeDevice();
+ }
+
+ @Override
+ public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd)
+ throws RemoteException {
+ return BackupTransport.this.performBackup(packageInfo, inFd);
+ }
+
+ @Override
+ public int clearBackupData(PackageInfo packageInfo) throws RemoteException {
+ return BackupTransport.this.clearBackupData(packageInfo);
+ }
+
+ @Override
+ public int finishBackup() throws RemoteException {
+ return BackupTransport.this.finishBackup();
+ }
+
+ @Override
+ public RestoreSet[] getAvailableRestoreSets() throws RemoteException {
+ return BackupTransport.this.getAvailableRestoreSets();
+ }
+
+ @Override
+ public long getCurrentRestoreSet() throws RemoteException {
+ return BackupTransport.this.getCurrentRestoreSet();
+ }
+
+ @Override
+ public int startRestore(long token, PackageInfo[] packages) throws RemoteException {
+ return BackupTransport.this.startRestore(token, packages);
+ }
+
+ @Override
+ public String nextRestorePackage() throws RemoteException {
+ return BackupTransport.this.nextRestorePackage();
+ }
+
+ @Override
+ public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
+ return BackupTransport.this.getRestoreData(outFd);
+ }
+
+ @Override
+ public void finishRestore() throws RemoteException {
+ BackupTransport.this.finishRestore();
+ }
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 1574090..d898060 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -104,6 +104,12 @@ public interface BluetoothProfile {
public static final int MAP = 9;
/**
+ * A2DP Sink Profile
+ * @hide
+ */
+ public static final int A2DP_SINK = 10;
+
+ /**
* Default priority for devices that we try to auto-connect to and
* and allow incoming connections for the profile
* @hide
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index d3a979c..c69e669 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2695,6 +2695,15 @@ public abstract class Context {
/**
* Use with {@link #getSystemService} to retrieve a
+ * {@link android.content.RestrictionsManager} for retrieving application restrictions
+ * and requesting permissions for restricted operations.
+ * @see #getSystemService
+ * @see android.content.RestrictionsManager
+ */
+ public static final String RESTRICTIONS_SERVICE = "restrictions";
+
+ /**
+ * Use with {@link #getSystemService} to retrieve a
* {@link android.app.AppOpsManager} for tracking application operations
* on the device.
*
diff --git a/core/java/android/content/IRestrictionsManager.aidl b/core/java/android/content/IRestrictionsManager.aidl
new file mode 100644
index 0000000..b1c0a3a
--- /dev/null
+++ b/core/java/android/content/IRestrictionsManager.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2014 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.content;
+
+import android.os.Bundle;
+
+/**
+ * Interface used by the RestrictionsManager
+ * @hide
+ */
+interface IRestrictionsManager {
+ Bundle getApplicationRestrictions(in String packageName);
+ boolean hasRestrictionsProvider();
+ void requestPermission(in String packageName, in String requestTemplate, in Bundle requestData);
+ void notifyPermissionResponse(in String packageName, in Bundle response);
+}
diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java
index 3ff53bf..62f88a9 100644
--- a/core/java/android/content/RestrictionEntry.java
+++ b/core/java/android/content/RestrictionEntry.java
@@ -73,32 +73,38 @@ public class RestrictionEntry implements Parcelable {
*/
public static final int TYPE_MULTI_SELECT = 4;
+ /**
+ * A type of restriction. Use this for storing an integer value. The range of values
+ * is from {@link Integer#MIN_VALUE} to {@link Integer#MAX_VALUE}.
+ */
+ public static final int TYPE_INTEGER = 5;
+
/** The type of restriction. */
- private int type;
+ private int mType;
/** The unique key that identifies the restriction. */
- private String key;
+ private String mKey;
/** The user-visible title of the restriction. */
- private String title;
+ private String mTitle;
/** The user-visible secondary description of the restriction. */
- private String description;
+ private String mDescription;
/** The user-visible set of choices used for single-select and multi-select lists. */
- private String [] choices;
+ private String [] mChoiceEntries;
/** The values corresponding to the user-visible choices. The value(s) of this entry will
* one or more of these, returned by {@link #getAllSelectedStrings()} and
* {@link #getSelectedString()}.
*/
- private String [] values;
+ private String [] mChoiceValues;
/* The chosen value, whose content depends on the type of the restriction. */
- private String currentValue;
+ private String mCurrentValue;
/* List of selected choices in the multi-select case. */
- private String[] currentValues;
+ private String[] mCurrentValues;
/**
* Constructor for {@link #TYPE_CHOICE} type.
@@ -106,9 +112,9 @@ public class RestrictionEntry implements Parcelable {
* @param selectedString the current value
*/
public RestrictionEntry(String key, String selectedString) {
- this.key = key;
- this.type = TYPE_CHOICE;
- this.currentValue = selectedString;
+ this.mKey = key;
+ this.mType = TYPE_CHOICE;
+ this.mCurrentValue = selectedString;
}
/**
@@ -117,8 +123,8 @@ public class RestrictionEntry implements Parcelable {
* @param selectedState whether this restriction is selected or not
*/
public RestrictionEntry(String key, boolean selectedState) {
- this.key = key;
- this.type = TYPE_BOOLEAN;
+ this.mKey = key;
+ this.mType = TYPE_BOOLEAN;
setSelectedState(selectedState);
}
@@ -128,9 +134,20 @@ public class RestrictionEntry implements Parcelable {
* @param selectedStrings the list of values that are currently selected
*/
public RestrictionEntry(String key, String[] selectedStrings) {
- this.key = key;
- this.type = TYPE_MULTI_SELECT;
- this.currentValues = selectedStrings;
+ this.mKey = key;
+ this.mType = TYPE_MULTI_SELECT;
+ this.mCurrentValues = selectedStrings;
+ }
+
+ /**
+ * Constructor for {@link #TYPE_INTEGER} type.
+ * @param key the unique key for this restriction
+ * @param selectedInt the integer value of the restriction
+ */
+ public RestrictionEntry(String key, int selectedInt) {
+ mKey = key;
+ mType = TYPE_INTEGER;
+ setIntValue(selectedInt);
}
/**
@@ -138,7 +155,7 @@ public class RestrictionEntry implements Parcelable {
* @param type the type for this restriction.
*/
public void setType(int type) {
- this.type = type;
+ this.mType = type;
}
/**
@@ -146,7 +163,7 @@ public class RestrictionEntry implements Parcelable {
* @return the type for this restriction
*/
public int getType() {
- return type;
+ return mType;
}
/**
@@ -155,7 +172,7 @@ public class RestrictionEntry implements Parcelable {
* single string values.
*/
public String getSelectedString() {
- return currentValue;
+ return mCurrentValue;
}
/**
@@ -164,7 +181,7 @@ public class RestrictionEntry implements Parcelable {
* null otherwise.
*/
public String[] getAllSelectedStrings() {
- return currentValues;
+ return mCurrentValues;
}
/**
@@ -172,7 +189,23 @@ public class RestrictionEntry implements Parcelable {
* @return the current selected state of the entry.
*/
public boolean getSelectedState() {
- return Boolean.parseBoolean(currentValue);
+ return Boolean.parseBoolean(mCurrentValue);
+ }
+
+ /**
+ * Returns the value of the entry as an integer when the type is {@link #TYPE_INTEGER}.
+ * @return the integer value of the entry.
+ */
+ public int getIntValue() {
+ return Integer.parseInt(mCurrentValue);
+ }
+
+ /**
+ * Sets the integer value of the entry when the type is {@link #TYPE_INTEGER}.
+ * @param value the integer value to set.
+ */
+ public void setIntValue(int value) {
+ mCurrentValue = Integer.toString(value);
}
/**
@@ -181,7 +214,7 @@ public class RestrictionEntry implements Parcelable {
* @param selectedString the string value to select.
*/
public void setSelectedString(String selectedString) {
- currentValue = selectedString;
+ mCurrentValue = selectedString;
}
/**
@@ -190,7 +223,7 @@ public class RestrictionEntry implements Parcelable {
* @param state the current selected state
*/
public void setSelectedState(boolean state) {
- currentValue = Boolean.toString(state);
+ mCurrentValue = Boolean.toString(state);
}
/**
@@ -199,7 +232,7 @@ public class RestrictionEntry implements Parcelable {
* @param allSelectedStrings the current list of selected values.
*/
public void setAllSelectedStrings(String[] allSelectedStrings) {
- currentValues = allSelectedStrings;
+ mCurrentValues = allSelectedStrings;
}
/**
@@ -216,7 +249,7 @@ public class RestrictionEntry implements Parcelable {
* @see #getAllSelectedStrings()
*/
public void setChoiceValues(String[] choiceValues) {
- values = choiceValues;
+ mChoiceValues = choiceValues;
}
/**
@@ -227,7 +260,7 @@ public class RestrictionEntry implements Parcelable {
* @see #setChoiceValues(String[])
*/
public void setChoiceValues(Context context, int stringArrayResId) {
- values = context.getResources().getStringArray(stringArrayResId);
+ mChoiceValues = context.getResources().getStringArray(stringArrayResId);
}
/**
@@ -235,7 +268,7 @@ public class RestrictionEntry implements Parcelable {
* @return the list of possible values.
*/
public String[] getChoiceValues() {
- return values;
+ return mChoiceValues;
}
/**
@@ -248,7 +281,7 @@ public class RestrictionEntry implements Parcelable {
* @see #setChoiceValues(String[])
*/
public void setChoiceEntries(String[] choiceEntries) {
- choices = choiceEntries;
+ mChoiceEntries = choiceEntries;
}
/** Sets a list of strings that will be presented as choices to the user. This is similar to
@@ -257,7 +290,7 @@ public class RestrictionEntry implements Parcelable {
* @param stringArrayResId the resource id of a string array containing the possible entries.
*/
public void setChoiceEntries(Context context, int stringArrayResId) {
- choices = context.getResources().getStringArray(stringArrayResId);
+ mChoiceEntries = context.getResources().getStringArray(stringArrayResId);
}
/**
@@ -265,7 +298,7 @@ public class RestrictionEntry implements Parcelable {
* @return the list of choices presented to the user.
*/
public String[] getChoiceEntries() {
- return choices;
+ return mChoiceEntries;
}
/**
@@ -273,7 +306,7 @@ public class RestrictionEntry implements Parcelable {
* @return the user-visible description, null if none was set earlier.
*/
public String getDescription() {
- return description;
+ return mDescription;
}
/**
@@ -283,7 +316,7 @@ public class RestrictionEntry implements Parcelable {
* @param description the user-visible description string.
*/
public void setDescription(String description) {
- this.description = description;
+ this.mDescription = description;
}
/**
@@ -291,7 +324,7 @@ public class RestrictionEntry implements Parcelable {
* @return the key for the restriction.
*/
public String getKey() {
- return key;
+ return mKey;
}
/**
@@ -299,7 +332,7 @@ public class RestrictionEntry implements Parcelable {
* @return the user-visible title for the entry, null if none was set earlier.
*/
public String getTitle() {
- return title;
+ return mTitle;
}
/**
@@ -307,7 +340,7 @@ public class RestrictionEntry implements Parcelable {
* @param title the user-visible title for the entry.
*/
public void setTitle(String title) {
- this.title = title;
+ this.mTitle = title;
}
private boolean equalArrays(String[] one, String[] other) {
@@ -324,23 +357,23 @@ public class RestrictionEntry implements Parcelable {
if (!(o instanceof RestrictionEntry)) return false;
final RestrictionEntry other = (RestrictionEntry) o;
// Make sure that either currentValue matches or currentValues matches.
- return type == other.type && key.equals(other.key)
+ return mType == other.mType && mKey.equals(other.mKey)
&&
- ((currentValues == null && other.currentValues == null
- && currentValue != null && currentValue.equals(other.currentValue))
+ ((mCurrentValues == null && other.mCurrentValues == null
+ && mCurrentValue != null && mCurrentValue.equals(other.mCurrentValue))
||
- (currentValue == null && other.currentValue == null
- && currentValues != null && equalArrays(currentValues, other.currentValues)));
+ (mCurrentValue == null && other.mCurrentValue == null
+ && mCurrentValues != null && equalArrays(mCurrentValues, other.mCurrentValues)));
}
@Override
public int hashCode() {
int result = 17;
- result = 31 * result + key.hashCode();
- if (currentValue != null) {
- result = 31 * result + currentValue.hashCode();
- } else if (currentValues != null) {
- for (String value : currentValues) {
+ result = 31 * result + mKey.hashCode();
+ if (mCurrentValue != null) {
+ result = 31 * result + mCurrentValue.hashCode();
+ } else if (mCurrentValues != null) {
+ for (String value : mCurrentValues) {
if (value != null) {
result = 31 * result + value.hashCode();
}
@@ -359,14 +392,14 @@ public class RestrictionEntry implements Parcelable {
}
public RestrictionEntry(Parcel in) {
- type = in.readInt();
- key = in.readString();
- title = in.readString();
- description = in.readString();
- choices = readArray(in);
- values = readArray(in);
- currentValue = in.readString();
- currentValues = readArray(in);
+ mType = in.readInt();
+ mKey = in.readString();
+ mTitle = in.readString();
+ mDescription = in.readString();
+ mChoiceEntries = readArray(in);
+ mChoiceValues = readArray(in);
+ mCurrentValue = in.readString();
+ mCurrentValues = readArray(in);
}
@Override
@@ -387,14 +420,14 @@ public class RestrictionEntry implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(type);
- dest.writeString(key);
- dest.writeString(title);
- dest.writeString(description);
- writeArray(dest, choices);
- writeArray(dest, values);
- dest.writeString(currentValue);
- writeArray(dest, currentValues);
+ dest.writeInt(mType);
+ dest.writeString(mKey);
+ dest.writeString(mTitle);
+ dest.writeString(mDescription);
+ writeArray(dest, mChoiceEntries);
+ writeArray(dest, mChoiceValues);
+ dest.writeString(mCurrentValue);
+ writeArray(dest, mCurrentValues);
}
public static final Creator<RestrictionEntry> CREATOR = new Creator<RestrictionEntry>() {
@@ -409,6 +442,6 @@ public class RestrictionEntry implements Parcelable {
@Override
public String toString() {
- return "RestrictionsEntry {type=" + type + ", key=" + key + ", value=" + currentValue + "}";
+ return "RestrictionsEntry {type=" + mType + ", key=" + mKey + ", value=" + mCurrentValue + "}";
}
}
diff --git a/core/java/android/content/RestrictionsManager.java b/core/java/android/content/RestrictionsManager.java
new file mode 100644
index 0000000..0dd0edd
--- /dev/null
+++ b/core/java/android/content/RestrictionsManager.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2014 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.content;
+
+import android.app.admin.DevicePolicyManager;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Provides a mechanism for apps to query restrictions imposed by an entity that
+ * manages the user. Apps can also send permission requests to a local or remote
+ * device administrator to override default app-specific restrictions or any other
+ * operation that needs explicit authorization from the administrator.
+ * <p>
+ * Apps can expose a set of restrictions via a runtime receiver mechanism or via
+ * static meta data in the manifest.
+ * <p>
+ * If the user has an active restrictions provider, dynamic requests can be made in
+ * addition to the statically imposed restrictions. Dynamic requests are app-specific
+ * and can be expressed via a predefined set of templates.
+ * <p>
+ * The RestrictionsManager forwards the dynamic requests to the active
+ * restrictions provider. The restrictions provider can respond back to requests by calling
+ * {@link #notifyPermissionResponse(String, Bundle)}, when
+ * a response is received from the administrator of the device or user
+ * The response is relayed back to the application via a protected broadcast,
+ * {@link #ACTION_PERMISSION_RESPONSE_RECEIVED}.
+ * <p>
+ * Static restrictions are specified by an XML file referenced by a meta-data attribute
+ * in the manifest. This enables applications as well as any web administration consoles
+ * to be able to read the template from the apk.
+ * <p>
+ * The syntax of the XML format is as follows:
+ * <pre>
+ * &lt;restrictions&gt;
+ * &lt;restriction
+ * android:key="&lt;key&gt;"
+ * android:restrictionType="boolean|string|integer|multi-select|null"
+ * ... /&gt;
+ * &lt;restriction ... /&gt;
+ * &lt;/restrictions&gt;
+ * </pre>
+ * <p>
+ * The attributes for each restriction depend on the restriction type.
+ *
+ * @see RestrictionEntry
+ */
+public class RestrictionsManager {
+
+ /**
+ * Broadcast intent delivered when a response is received for a permission
+ * request. The response is not available for later query, so the receiver
+ * must persist and/or immediately act upon the response. The application
+ * should not interrupt the user by coming to the foreground if it isn't
+ * currently in the foreground. It can post a notification instead, informing
+ * the user of a change in state.
+ * <p>
+ * For instance, if the user requested permission to make an in-app purchase,
+ * the app can post a notification that the request had been granted or denied,
+ * and allow the purchase to go through.
+ * <p>
+ * The broadcast Intent carries the following extra:
+ * {@link #EXTRA_RESPONSE_BUNDLE}.
+ */
+ public static final String ACTION_PERMISSION_RESPONSE_RECEIVED =
+ "android.intent.action.PERMISSION_RESPONSE_RECEIVED";
+
+ /**
+ * Protected broadcast intent sent to the active restrictions provider. The intent
+ * contains the following extras:<p>
+ * <ul>
+ * <li>{@link #EXTRA_PACKAGE_NAME} : String; the package name of the application requesting
+ * permission.</li>
+ * <li>{@link #EXTRA_TEMPLATE_ID} : String; the template of the request.</li>
+ * <li>{@link #EXTRA_REQUEST_BUNDLE} : Bundle; contains the template-specific keys and values
+ * for the request.
+ * </ul>
+ * @see DevicePolicyManager#setRestrictionsProvider(ComponentName, ComponentName)
+ * @see #requestPermission(String, String, Bundle)
+ */
+ public static final String ACTION_REQUEST_PERMISSION =
+ "android.intent.action.REQUEST_PERMISSION";
+
+ /**
+ * The package name of the application making the request.
+ */
+ public static final String EXTRA_PACKAGE_NAME = "package_name";
+
+ /**
+ * The template id that specifies what kind of a request it is and may indicate
+ * how the request is to be presented to the administrator. Must be either one of
+ * the predefined templates or a custom one specified by the application that the
+ * restrictions provider is familiar with.
+ */
+ public static final String EXTRA_TEMPLATE_ID = "template_id";
+
+ /**
+ * A bundle containing the details about the request. The contents depend on the
+ * template id.
+ * @see #EXTRA_TEMPLATE_ID
+ */
+ public static final String EXTRA_REQUEST_BUNDLE = "request_bundle";
+
+ /**
+ * Contains a response from the administrator for specific request.
+ * The bundle contains the following information, at least:
+ * <ul>
+ * <li>{@link #REQUEST_KEY_ID}: The request id.</li>
+ * <li>{@link #REQUEST_KEY_DATA}: The request reference data.</li>
+ * </ul>
+ * <p>
+ * And depending on what the request template was, the bundle will contain the actual
+ * result of the request. For {@link #REQUEST_TEMPLATE_QUESTION}, the result will be in
+ * {@link #RESPONSE_KEY_BOOLEAN}, which is of type boolean; true if the administrator
+ * approved the request, false otherwise.
+ */
+ public static final String EXTRA_RESPONSE_BUNDLE = "response_bundle";
+
+
+ /**
+ * Request template that presents a simple question, with a possible title and icon.
+ * <p>
+ * Required keys are
+ * {@link #REQUEST_KEY_ID} and {@link #REQUEST_KEY_MESSAGE}.
+ * <p>
+ * Optional keys are
+ * {@link #REQUEST_KEY_DATA}, {@link #REQUEST_KEY_ICON}, {@link #REQUEST_KEY_TITLE},
+ * {@link #REQUEST_KEY_APPROVE_LABEL} and {@link #REQUEST_KEY_DENY_LABEL}.
+ */
+ public static final String REQUEST_TEMPLATE_QUESTION = "android.req_template.type.simple";
+
+ /**
+ * Key for request ID contained in the request bundle.
+ * <p>
+ * App-generated request id to identify the specific request when receiving
+ * a response. This value is returned in the {@link #EXTRA_RESPONSE_BUNDLE}.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_ID = "android.req_template.req_id";
+
+ /**
+ * Key for request data contained in the request bundle.
+ * <p>
+ * Optional, typically used to identify the specific data that is being referred to,
+ * such as the unique identifier for a movie or book. This is not used for display
+ * purposes and is more like a cookie. This value is returned in the
+ * {@link #EXTRA_RESPONSE_BUNDLE}.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_DATA = "android.req_template.data";
+
+ /**
+ * Key for request title contained in the request bundle.
+ * <p>
+ * Optional, typically used as the title of any notification or dialog presented
+ * to the administrator who approves the request.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_TITLE = "android.req_template.title";
+
+ /**
+ * Key for request message contained in the request bundle.
+ * <p>
+ * Required, shown as the actual message in a notification or dialog presented
+ * to the administrator who approves the request.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_MESSAGE = "android.req_template.mesg";
+
+ /**
+ * Key for request icon contained in the request bundle.
+ * <p>
+ * Optional, shown alongside the request message presented to the administrator
+ * who approves the request.
+ * <p>
+ * Type: Bitmap
+ */
+ public static final String REQUEST_KEY_ICON = "android.req_template.icon";
+
+ /**
+ * Key for request approval button label contained in the request bundle.
+ * <p>
+ * Optional, may be shown as a label on the positive button in a dialog or
+ * notification presented to the administrator who approves the request.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_APPROVE_LABEL = "android.req_template.accept";
+
+ /**
+ * Key for request rejection button label contained in the request bundle.
+ * <p>
+ * Optional, may be shown as a label on the negative button in a dialog or
+ * notification presented to the administrator who approves the request.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_DENY_LABEL = "android.req_template.reject";
+
+ /**
+ * Key for requestor's name contained in the request bundle. This value is not specified by
+ * the application. It is automatically inserted into the Bundle by the Restrictions Provider
+ * before it is sent to the administrator.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_REQUESTOR_NAME = "android.req_template.requestor";
+
+ /**
+ * Key for requestor's device name contained in the request bundle. This value is not specified
+ * by the application. It is automatically inserted into the Bundle by the Restrictions Provider
+ * before it is sent to the administrator.
+ * <p>
+ * Type: String
+ */
+ public static final String REQUEST_KEY_DEVICE_NAME = "android.req_template.device";
+
+ /**
+ * Key for the response in the response bundle sent to the application, for a permission
+ * request.
+ * <p>
+ * Type: boolean
+ */
+ public static final String RESPONSE_KEY_BOOLEAN = "android.req_template.response";
+
+ private static final String TAG = "RestrictionsManager";
+
+ private final Context mContext;
+ private final IRestrictionsManager mService;
+
+ /**
+ * @hide
+ */
+ public RestrictionsManager(Context context, IRestrictionsManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Returns any available set of application-specific restrictions applicable
+ * to this application.
+ * @return
+ */
+ public Bundle getApplicationRestrictions() {
+ try {
+ if (mService != null) {
+ return mService.getApplicationRestrictions(mContext.getPackageName());
+ }
+ } catch (RemoteException re) {
+ Log.w(TAG, "Couldn't reach service");
+ }
+ return null;
+ }
+
+ /**
+ * Called by an application to check if permission requests can be made. If false,
+ * there is no need to request permission for an operation, unless a static
+ * restriction applies to that operation.
+ * @return
+ */
+ public boolean hasRestrictionsProvider() {
+ try {
+ if (mService != null) {
+ return mService.hasRestrictionsProvider();
+ }
+ } catch (RemoteException re) {
+ Log.w(TAG, "Couldn't reach service");
+ }
+ return false;
+ }
+
+ /**
+ * Called by an application to request permission for an operation. The contents of the
+ * request are passed in a Bundle that contains several pieces of data depending on the
+ * chosen request template.
+ *
+ * @param requestTemplate The request template to use. The template could be one of the
+ * predefined templates specified in this class or a custom template that the specific
+ * Restrictions Provider might understand. For custom templates, the template name should be
+ * namespaced to avoid collisions with predefined templates and templates specified by
+ * other Restrictions Provider vendors.
+ * @param requestData A Bundle containing the data corresponding to the specified request
+ * template. The keys for the data in the bundle depend on the kind of template chosen.
+ */
+ public void requestPermission(String requestTemplate, Bundle requestData) {
+ try {
+ if (mService != null) {
+ mService.requestPermission(mContext.getPackageName(), requestTemplate, requestData);
+ }
+ } catch (RemoteException re) {
+ Log.w(TAG, "Couldn't reach service");
+ }
+ }
+
+ /**
+ * Called by the Restrictions Provider when a response is available to be
+ * delivered to an application.
+ * @param packageName
+ * @param response
+ */
+ public void notifyPermissionResponse(String packageName, Bundle response) {
+ try {
+ if (mService != null) {
+ mService.notifyPermissionResponse(packageName, response);
+ }
+ } catch (RemoteException re) {
+ Log.w(TAG, "Couldn't reach service");
+ }
+ }
+
+ /**
+ * Parse and return the list of restrictions defined in the manifest for the specified
+ * package, if any.
+ * @param packageName The application for which to fetch the restrictions list.
+ * @return The list of RestrictionEntry objects created from the XML file specified
+ * in the manifest, or null if none was specified.
+ */
+ public List<RestrictionEntry> getManifestRestrictions(String packageName) {
+ // TODO:
+ return null;
+ }
+}
diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java
index 6b4404d..46c9234 100644
--- a/core/java/android/content/SharedPreferences.java
+++ b/core/java/android/content/SharedPreferences.java
@@ -355,7 +355,14 @@ public interface SharedPreferences {
/**
* Registers a callback to be invoked when a change happens to a preference.
- *
+ *
+ * <p class="caution"><strong>Caution:</strong> The preference manager does
+ * not currently store a strong reference to the listener. You must store a
+ * strong reference to the listener, or it will be susceptible to garbage
+ * collection. We recommend you keep a reference to the listener in the
+ * instance data of an object that will exist as long as you need the
+ * listener.</p>
+ *
* @param listener The callback that will run.
* @see #unregisterOnSharedPreferenceChangeListener
*/
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 6cb781f..44a6a5d 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -112,7 +112,7 @@ interface IPackageManager {
ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags, int userId);
- boolean canForwardTo(in Intent intent, String resolvedType, int userIdFrom, int userIdDest);
+ boolean canForwardTo(in Intent intent, String resolvedType, int sourceUserId, int targetUserId);
List<ResolveInfo> queryIntentActivities(in Intent intent,
String resolvedType, int flags, int userId);
@@ -248,10 +248,10 @@ interface IPackageManager {
void clearPackagePersistentPreferredActivities(String packageName, int userId);
- void addForwardingIntentFilter(in IntentFilter filter, boolean removable, int userIdOrig,
- int userIdDest);
+ void addCrossProfileIntentFilter(in IntentFilter filter, boolean removable, int sourceUserId,
+ int targetUserId);
- void clearForwardingIntentFilters(int userIdOrig);
+ void clearCrossProfileIntentFilters(int sourceUserId);
/**
* Report the set of 'Home' activity candidates, plus (if any) which of them
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index d7bd473..4672015 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -19,9 +19,12 @@ package android.content.pm;
import android.app.PackageInstallObserver;
import android.app.PackageUninstallObserver;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.FileBridge;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import java.io.OutputStream;
+
/** {@hide} */
public class PackageInstaller {
private final PackageManager mPm;
@@ -127,10 +130,17 @@ public class PackageInstaller {
}
}
- public ParcelFileDescriptor openWrite(String overlayName, long offsetBytes,
- long lengthBytes) {
+ /**
+ * Open an APK file for writing, starting at the given offset. You can
+ * then stream data into the file, periodically calling
+ * {@link OutputStream#flush()} to ensure bytes have been written to
+ * disk.
+ */
+ public OutputStream openWrite(String splitName, long offsetBytes, long lengthBytes) {
try {
- return mSession.openWrite(overlayName, offsetBytes, lengthBytes);
+ final ParcelFileDescriptor clientSocket = mSession.openWrite(splitName,
+ offsetBytes, lengthBytes);
+ return new FileBridge.FileBridgeOutputStream(clientSocket.getFileDescriptor());
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 35bcc02..c5cd5c9 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3576,24 +3576,38 @@ public abstract class PackageManager {
}
/**
- * Adds a forwarding intent filter. After calling this method all intents sent from the user
- * with id userIdOrig can also be be resolved by activities in the user with id userIdDest if
- * they match the specified intent filter.
- * @param filter the {@link IntentFilter} the intent has to match to be forwarded
- * @param removable if set to false, {@link clearForwardingIntents} will not remove this intent
- * filter
- * @param userIdOrig user from which the intent can be forwarded
- * @param userIdDest user to which the intent can be forwarded
+ * Adds a {@link CrossProfileIntentFilter}. After calling this method all intents sent from the
+ * user with id sourceUserId can also be be resolved by activities in the user with id
+ * targetUserId if they match the specified intent filter.
+ * @param filter the {@link IntentFilter} the intent has to match
+ * @param removable if set to false, {@link clearCrossProfileIntentFilters} will not remove this
+ * {@link CrossProfileIntentFilter}
* @hide
*/
+ public abstract void addCrossProfileIntentFilter(IntentFilter filter, boolean removable,
+ int sourceUserId, int targetUserId);
+
+ /**
+ * @hide
+ * @deprecated
+ * TODO: remove it as soon as the code of ManagedProvisionning is updated
+ */
public abstract void addForwardingIntentFilter(IntentFilter filter, boolean removable,
- int userIdOrig, int userIdDest);
+ int sourceUserId, int targetUserId);
/**
- * Clearing all removable {@link ForwardingIntentFilter}s that are set with the given user as
- * the origin.
- * @param userIdOrig user from which the intent can be forwarded
+ * Clearing removable {@link CrossProfileIntentFilter}s which have the specified user as their
+ * source
+ * @param sourceUserId
+ * be cleared.
* @hide
*/
- public abstract void clearForwardingIntentFilters(int userIdOrig);
+ public abstract void clearCrossProfileIntentFilters(int sourceUserId);
+
+ /**
+ * @hide
+ * @deprecated
+ * TODO: remove it as soon as the code of ManagedProvisionning is updated
+ */
+ public abstract void clearForwardingIntentFilters(int sourceUserId);
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 1c838c3..ab8bf61 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2174,7 +2174,6 @@ public class PackageParser {
}
final int innerDepth = parser.getDepth();
-
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
@@ -2548,13 +2547,13 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestActivity_singleUser,
false)) {
a.info.flags |= ActivityInfo.FLAG_SINGLE_USER;
- if (a.info.exported) {
+ if (a.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
Slog.w(TAG, "Activity exported request ignored due to singleUser: "
+ a.className + " at " + mArchiveSourcePath + " "
+ parser.getPositionDescription());
a.info.exported = false;
+ setExported = true;
}
- setExported = true;
}
if (sa.getBoolean(
com.android.internal.R.styleable.AndroidManifestActivity_primaryUserOnly,
@@ -2907,7 +2906,7 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestProvider_singleUser,
false)) {
p.info.flags |= ProviderInfo.FLAG_SINGLE_USER;
- if (p.info.exported) {
+ if (p.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
Slog.w(TAG, "Provider exported request ignored due to singleUser: "
+ p.className + " at " + mArchiveSourcePath + " "
+ parser.getPositionDescription());
@@ -3181,13 +3180,13 @@ public class PackageParser {
com.android.internal.R.styleable.AndroidManifestService_singleUser,
false)) {
s.info.flags |= ServiceInfo.FLAG_SINGLE_USER;
- if (s.info.exported) {
+ if (s.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
Slog.w(TAG, "Service exported request ignored due to singleUser: "
+ s.className + " at " + mArchiveSourcePath + " "
+ parser.getPositionDescription());
s.info.exported = false;
+ setExported = true;
}
- setExported = true;
}
sa.recycle();
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index 86208fc..c8de2f1 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -142,9 +142,10 @@ public final class Sensor {
public static final String STRING_TYPE_TEMPERATURE = "android.sensor.temperature";
/**
- * A constant describing a proximity sensor type.
+ * A constant describing a proximity sensor type. This is a wake up sensor.
* <p>See {@link android.hardware.SensorEvent#values SensorEvent.values}
* for more details.
+ * @see #isWakeUpSensor()
*/
public static final int TYPE_PROXIMITY = 8;
@@ -307,8 +308,10 @@ public final class Sensor {
* itself. The sensor continues to operate while the device is asleep
* and will automatically wake the device to notify when significant
* motion is detected. The application does not need to hold any wake
- * locks for this sensor to trigger.
+ * locks for this sensor to trigger. This is a wake up sensor.
* <p>See {@link TriggerEvent} for more details.
+ *
+ * @see #isWakeUpSensor()
*/
public static final int TYPE_SIGNIFICANT_MOTION = 17;
@@ -381,11 +384,17 @@ public final class Sensor {
/**
* A constant describing a heart rate monitor.
* <p>
- * A sensor that measures the heart rate in beats per minute.
+ * The reported value is the heart rate in beats per minute.
+ * <p>
+ * The reported accuracy represents the status of the monitor during the reading. See the
+ * {@code SENSOR_STATUS_*} constants in {@link android.hardware.SensorManager SensorManager}
+ * for more details on accuracy/status values. In particular, when the accuracy is
+ * {@code SENSOR_STATUS_UNRELIABLE} or {@code SENSOR_STATUS_NO_CONTACT}, the heart rate
+ * value should be discarded.
* <p>
- * value[0] represents the beats per minute when the measurement was taken.
- * value[0] is 0 if the heart rate monitor could not measure the rate or the
- * rate is 0 beat per minute.
+ * This sensor requires permission {@code android.permission.BODY_SENSORS}.
+ * It will not be returned by {@code SensorManager.getSensorsList} nor
+ * {@code SensorManager.getDefaultSensor} if the application doesn't have this permission.
*/
public static final int TYPE_HEART_RATE = 21;
@@ -397,6 +406,321 @@ public final class Sensor {
public static final String STRING_TYPE_HEART_RATE = "android.sensor.heart_rate";
/**
+ * A non-wake up variant of proximity sensor.
+ *
+ * @see #TYPE_PROXIMITY
+ */
+ public static final int TYPE_NON_WAKE_UP_PROXIMITY_SENSOR = 22;
+
+ /**
+ * A constant string describing a non-wake up proximity sensor type.
+ *
+ * @see #TYPE_NON_WAKE_UP_PROXIMITY_SENSOR
+ */
+ public static final String SENSOR_STRING_TYPE_NON_WAKE_UP_PROXIMITY_SENSOR =
+ "android.sensor.non_wake_up_proximity_sensor";
+
+ /**
+ * A constant describing a wake up variant of accelerometer sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_ACCELEROMETER
+ */
+ public static final int TYPE_WAKE_UP_ACCELEROMETER = 23;
+
+ /**
+ * A constant string describing a wake up accelerometer.
+ *
+ * @see #TYPE_WAKE_UP_ACCELEROMETER
+ */
+ public static final String STRING_TYPE_WAKE_UP_ACCELEROMETER =
+ "android.sensor.wake_up_accelerometer";
+
+ /**
+ * A constant describing a wake up variant of a magnetic field sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_MAGNETIC_FIELD
+ */
+ public static final int TYPE_WAKE_UP_MAGNETIC_FIELD = 24;
+
+ /**
+ * A constant string describing a wake up magnetic field sensor.
+ *
+ * @see #TYPE_WAKE_UP_MAGNETIC_FIELD
+ */
+ public static final String STRING_TYPE_WAKE_UP_MAGNETIC_FIELD =
+ "android.sensor.wake_up_magnetic_field";
+
+ /**
+ * A constant describing a wake up variant of an orientation sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_ORIENTATION
+ */
+ public static final int TYPE_WAKE_UP_ORIENTATION = 25;
+
+ /**
+ * A constant string describing a wake up orientation sensor.
+ *
+ * @see #TYPE_WAKE_UP_ORIENTATION
+ */
+ public static final String STRING_TYPE_WAKE_UP_ORIENTATION =
+ "android.sensor.wake_up_orientation";
+
+ /**
+ * A constant describing a wake up variant of a gyroscope sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_GYROSCOPE
+ */
+ public static final int TYPE_WAKE_UP_GYROSCOPE = 26;
+
+ /**
+ * A constant string describing a wake up gyroscope sensor type.
+ *
+ * @see #TYPE_WAKE_UP_GYROSCOPE
+ */
+ public static final String STRING_TYPE_WAKE_UP_GYROSCOPE = "android.sensor.wake_up_gyroscope";
+
+ /**
+ * A constant describing a wake up variant of a light sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_LIGHT
+ */
+ public static final int TYPE_WAKE_UP_LIGHT = 27;
+
+ /**
+ * A constant string describing a wake up light sensor type.
+ *
+ * @see #TYPE_WAKE_UP_LIGHT
+ */
+ public static final String STRING_TYPE_WAKE_UP_LIGHT = "android.sensor.wake_up_light";
+
+ /**
+ * A constant describing a wake up variant of a pressure sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_PRESSURE
+ */
+ public static final int TYPE_WAKE_UP_PRESSURE = 28;
+
+ /**
+ * A constant string describing a wake up pressure sensor type.
+ *
+ * @see #TYPE_WAKE_UP_PRESSURE
+ */
+ public static final String STRING_TYPE_WAKE_UP_PRESSURE = "android.sensor.wake_up_pressure";
+
+ /**
+ * A constant describing a wake up variant of a gravity sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_GRAVITY
+ */
+ public static final int TYPE_WAKE_UP_GRAVITY = 29;
+
+ /**
+ * A constant string describing a wake up gravity sensor type.
+ *
+ * @see #TYPE_WAKE_UP_GRAVITY
+ */
+ public static final String STRING_TYPE_WAKE_UP_GRAVITY = "android.sensor.wake_up_gravity";
+
+ /**
+ * A constant describing a wake up variant of a linear acceleration sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_LINEAR_ACCELERATION
+ */
+ public static final int TYPE_WAKE_UP_LINEAR_ACCELERATION = 30;
+
+ /**
+ * A constant string describing a wake up linear acceleration sensor type.
+ *
+ * @see #TYPE_WAKE_UP_LINEAR_ACCELERATION
+ */
+ public static final String STRING_TYPE_WAKE_UP_LINEAR_ACCELERATION =
+ "android.sensor.wake_up_linear_acceleration";
+
+ /**
+ * A constant describing a wake up variant of a rotation vector sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_ROTATION_VECTOR
+ */
+ public static final int TYPE_WAKE_UP_ROTATION_VECTOR = 31;
+
+ /**
+ * A constant string describing a wake up rotation vector sensor type.
+ *
+ * @see #TYPE_WAKE_UP_ROTATION_VECTOR
+ */
+ public static final String STRING_TYPE_WAKE_UP_ROTATION_VECTOR =
+ "android.sensor.wake_up_rotation_vector";
+
+ /**
+ * A constant describing a wake up variant of a relative humidity sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_RELATIVE_HUMIDITY
+ */
+ public static final int TYPE_WAKE_UP_RELATIVE_HUMIDITY = 32;
+
+ /**
+ * A constant string describing a wake up relative humidity sensor type.
+ *
+ * @see #TYPE_WAKE_UP_RELATIVE_HUMIDITY
+ */
+ public static final String STRING_TYPE_WAKE_UP_RELATIVE_HUMIDITY =
+ "android.sensor.wake_up_relative_humidity";
+
+ /**
+ * A constant describing a wake up variant of an ambient temperature sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_AMBIENT_TEMPERATURE
+ */
+ public static final int TYPE_WAKE_UP_AMBIENT_TEMPERATURE = 33;
+
+ /**
+ * A constant string describing a wake up ambient temperature sensor type.
+ *
+ * @see #TYPE_WAKE_UP_AMBIENT_TEMPERATURE
+ */
+ public static final String STRING_TYPE_WAKE_UP_AMBIENT_TEMPERATURE =
+ "android.sensor.wake_up_ambient_temperature";
+
+ /**
+ * A constant describing a wake up variant of an uncalibrated magnetic field sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_MAGNETIC_FIELD_UNCALIBRATED
+ */
+ public static final int TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED = 34;
+
+ /**
+ * A constant string describing a wake up uncalibrated magnetic field sensor type.
+ *
+ * @see #TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED
+ */
+ public static final String STRING_TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED =
+ "android.sensor.wake_up_magnetic_field_uncalibrated";
+
+ /**
+ * A constant describing a wake up variant of a game rotation vector sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_GAME_ROTATION_VECTOR
+ */
+ public static final int TYPE_WAKE_UP_GAME_ROTATION_VECTOR = 35;
+
+ /**
+ * A constant string describing a wake up game rotation vector sensor type.
+ *
+ * @see #TYPE_WAKE_UP_GAME_ROTATION_VECTOR
+ */
+ public static final String STRING_TYPE_WAKE_UP_GAME_ROTATION_VECTOR =
+ "android.sensor.wake_up_game_rotation_vector";
+
+ /**
+ * A constant describing a wake up variant of an uncalibrated gyroscope sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_GYROSCOPE_UNCALIBRATED
+ */
+ public static final int TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED = 36;
+
+ /**
+ * A constant string describing a wake up uncalibrated gyroscope sensor type.
+ *
+ * @see #TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED
+ */
+ public static final String STRING_TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED =
+ "android.sensor.wake_up_gyroscope_uncalibrated";
+
+ /**
+ * A constant describing a wake up variant of a step detector sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_STEP_DETECTOR
+ */
+ public static final int TYPE_WAKE_UP_STEP_DETECTOR = 37;
+
+ /**
+ * A constant string describing a wake up step detector sensor type.
+ *
+ * @see #TYPE_WAKE_UP_STEP_DETECTOR
+ */
+ public static final String STRING_TYPE_WAKE_UP_STEP_DETECTOR =
+ "android.sensor.wake_up_step_detector";
+
+ /**
+ * A constant describing a wake up variant of a step counter sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_STEP_COUNTER
+ */
+ public static final int TYPE_WAKE_UP_STEP_COUNTER = 38;
+
+ /**
+ * A constant string describing a wake up step counter sensor type.
+ *
+ * @see #TYPE_WAKE_UP_STEP_COUNTER
+ */
+ public static final String STRING_TYPE_WAKE_UP_STEP_COUNTER =
+ "android.sensor.wake_up_step_counter";
+
+ /**
+ * A constant describing a wake up variant of a geomagnetic rotation vector sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_GEOMAGNETIC_ROTATION_VECTOR
+ */
+ public static final int TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR = 39;
+
+ /**
+ * A constant string describing a wake up geomagnetic rotation vector sensor type.
+ *
+ * @see #TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR
+ */
+ public static final String STRING_TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR =
+ "android.sensor.wake_up_geomagnetic_rotation_vector";
+
+ /**
+ * A constant describing a wake up variant of a heart rate sensor type.
+ *
+ * @see #isWakeUpSensor()
+ * @see #TYPE_HEART_RATE
+ */
+ public static final int TYPE_WAKE_UP_HEART_RATE = 40;
+
+ /**
+ * A constant string describing a wake up heart rate sensor type.
+ *
+ * @see #TYPE_WAKE_UP_HEART_RATE
+ */
+ public static final String STRING_TYPE_WAKE_UP_HEART_RATE = "android.sensor.wake_up_heart_rate";
+
+ /**
+ * A sensor of this type generates an event each time a tilt event is detected. A tilt event
+ * is generated if the direction of the 2-seconds window average gravity changed by at
+ * least 35 degrees since the activation of the sensor. It is a wake up sensor.
+ *
+ * @see #isWakeUpSensor()
+ */
+ public static final int TYPE_WAKE_UP_TILT_DETECTOR = 41;
+
+ /**
+ * A constant string describing a wake up tilt detector sensor type.
+ *
+ * @see #TYPE_WAKE_UP_TILT_DETECTOR
+ */
+ public static final String SENSOR_STRING_TYPE_WAKE_UP_TILT_DETECTOR =
+ "android.sensor.wake_up_tilt_detector";
+
+ /**
* A constant describing a wake gesture sensor.
* <p>
* Wake gesture sensors enable waking up the device based on a device specific motion.
@@ -410,6 +734,7 @@ public final class Sensor {
* the device. This sensor must be low power, as it is likely to be activated 24/7.
* Values of events created by this sensors should not be used.
*
+ * @see #isWakeUpSensor()
* @hide This sensor is expected to only be used by the power manager
*/
public static final int TYPE_WAKE_GESTURE = 42;
@@ -467,7 +792,29 @@ public final class Sensor {
REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_STEP_DETECTOR
REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_STEP_COUNTER
REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR
- REPORTING_MODE_ON_CHANGE, 1 // SENSOR_TYPE_HEART_RATE_MONITOR
+ REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_HEART_RATE_MONITOR
+ REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_NON_WAKE_UP_PROXIMITY_SENSOR
+ // wake up variants of base sensors
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_ACCELEROMETER
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_MAGNETIC_FIELD
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_ORIENTATION
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_GYROSCOPE
+ REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_WAKE_UP_LIGHT
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_PRESSURE
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_GRAVITY
+ REPORTING_MODE_CONTINUOUS, 3, // SENSOR_TYPE_WAKE_UP_LINEAR_ACCELERATION
+ REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_WAKE_UP_ROTATION_VECTOR
+ REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_WAKE_UP_RELATIVE_HUMIDITY
+ REPORTING_MODE_ON_CHANGE, 3, // SENSOR_TYPE_WAKE_UP_AMBIENT_TEMPERATURE
+ REPORTING_MODE_CONTINUOUS, 6, // SENSOR_TYPE_WAKE_UP_MAGNETIC_FIELD_UNCALIBRATED
+ REPORTING_MODE_CONTINUOUS, 4, // SENSOR_TYPE_WAKE_UP_GAME_ROTATION_VECTOR
+ REPORTING_MODE_CONTINUOUS, 6, // SENSOR_TYPE_WAKE_UP_GYROSCOPE_UNCALIBRATED
+ REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_WAKE_UP_STEP_DETECTOR
+ REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_WAKE_UP_STEP_COUNTER
+ REPORTING_MODE_CONTINUOUS, 5, // SENSOR_TYPE_WAKE_UP_GEOMAGNETIC_ROTATION_VECTOR
+ REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_WAKE_UP_HEART_RATE_MONITOR
+ REPORTING_MODE_ON_CHANGE, 1, // SENSOR_TYPE_WAKE_UP_TILT_DETECTOR
+ REPORTING_MODE_ONE_SHOT, 1, // SENSOR_TYPE_WAKE_GESTURE
};
static int getReportingMode(Sensor sensor) {
@@ -525,6 +872,8 @@ public final class Sensor {
private int mFifoMaxEventCount;
private String mStringType;
private String mRequiredPermission;
+ private int mMaxDelay;
+ private boolean mWakeUpSensor;
Sensor() {
}
@@ -613,6 +962,7 @@ public final class Sensor {
}
/**
+ * @hide
* @return The permission required to access this sensor. If empty, no permission is required.
*/
public String getRequiredPermission() {
@@ -624,6 +974,51 @@ public final class Sensor {
return mHandle;
}
+ /**
+ * This value is defined only for continuous mode sensors. It is the delay between two
+ * sensor events corresponding to the lowest frequency that this sensor supports. When
+ * lower frequencies are requested through registerListener() the events will be generated
+ * at this frequency instead. It can be used to estimate when the batch FIFO may be full.
+ * Older devices may set this value to zero. Ignore this value in case it is negative
+ * or zero.
+ *
+ * @return The max delay for this sensor in microseconds.
+ */
+ public int getMaxDelay() {
+ return mMaxDelay;
+ }
+
+ /**
+ * Returns whether this sensor is a wake-up sensor.
+ * <p>
+ * Wake up sensors wake the application processor up when they have events to deliver. When a
+ * wake up sensor is registered to without batching enabled, each event will wake the
+ * application processor up.
+ * <p>
+ * When a wake up sensor is registered to with batching enabled, it
+ * wakes the application processor up when maxReportingLatency has elapsed or when the hardware
+ * FIFO storing the events from wake up sensors is getting full.
+ * <p>
+ * Non-wake up sensors never wake the application processor up. Their events are only reported
+ * when the application processor is awake, for example because the application holds a wake
+ * lock, or another source woke the application processor up.
+ * <p>
+ * When a non-wake up sensor is registered to without batching enabled, the measurements made
+ * while the application processor is asleep might be lost and never returned.
+ * <p>
+ * When a non-wake up sensor is registered to with batching enabled, the measurements made while
+ * the application processor is asleep are stored in the hardware FIFO for non-wake up sensors.
+ * When this FIFO gets full, new events start overwriting older events. When the application
+ * then wakes up, the latest events are returned, and some old events might be lost. The number
+ * of events actually returned depends on the hardware FIFO size, as well as on what other
+ * sensors are activated. If losing sensor events is not acceptable during batching, you must
+ * use the wake-up version of the sensor.
+ * @return true if this is a wake up sensor, false otherwise.
+ */
+ public boolean isWakeUpSensor() {
+ return mWakeUpSensor;
+ }
+
void setRange(float max, float res) {
mMaxRange = max;
mResolution = res;
diff --git a/core/java/android/hardware/SensorEventListener.java b/core/java/android/hardware/SensorEventListener.java
index 677d244..0d859fb 100644
--- a/core/java/android/hardware/SensorEventListener.java
+++ b/core/java/android/hardware/SensorEventListener.java
@@ -39,11 +39,13 @@ public interface SensorEventListener {
public void onSensorChanged(SensorEvent event);
/**
- * Called when the accuracy of a sensor has changed.
- * <p>See {@link android.hardware.SensorManager SensorManager}
- * for details.
+ * Called when the accuracy of the registered sensor has changed.
+ *
+ * <p>See the SENSOR_STATUS_* constants in
+ * {@link android.hardware.SensorManager SensorManager} for details.
*
- * @param accuracy The new accuracy of this sensor
+ * @param accuracy The new accuracy of this sensor, one of
+ * {@code SensorManager.SENSOR_STATUS_*}
*/
- public void onAccuracyChanged(Sensor sensor, int accuracy);
+ public void onAccuracyChanged(Sensor sensor, int accuracy);
}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 5f2b5f0..25c7630 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -321,6 +321,13 @@ public abstract class SensorManager {
/**
+ * The values returned by this sensor cannot be trusted because the sensor
+ * had no contact with what it was measuring (for example, the heart rate
+ * monitor is not in contact with the user).
+ */
+ public static final int SENSOR_STATUS_NO_CONTACT = -1;
+
+ /**
* The values returned by this sensor cannot be trusted, calibration is
* needed or the environment doesn't allow readings
*/
@@ -421,9 +428,10 @@ public abstract class SensorManager {
* {@link SensorManager#getSensorList(int) getSensorList}.
*
* @param type
- * of sensors requested
+ * of sensors requested
*
- * @return the default sensors matching the asked type.
+ * @return the default sensor matching the requested type if one exists and the application
+ * has the necessary permissions, or null otherwise.
*
* @see #getSensorList(int)
* @see Sensor
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 8684a04..b66ec86 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -395,25 +395,12 @@ public class SystemSensorManager extends SensorManager {
t.timestamp = timestamp;
t.accuracy = inAccuracy;
t.sensor = sensor;
- switch (t.sensor.getType()) {
- // Only report accuracy for sensors that support it.
- case Sensor.TYPE_MAGNETIC_FIELD:
- case Sensor.TYPE_ORIENTATION:
- // call onAccuracyChanged() only if the value changes
- final int accuracy = mSensorAccuracies.get(handle);
- if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {
- mSensorAccuracies.put(handle, t.accuracy);
- mListener.onAccuracyChanged(t.sensor, t.accuracy);
- }
- break;
- default:
- // For other sensors, just report the accuracy once
- if (mFirstEvent.get(handle) == false) {
- mFirstEvent.put(handle, true);
- mListener.onAccuracyChanged(
- t.sensor, SENSOR_STATUS_ACCURACY_HIGH);
- }
- break;
+
+ // call onAccuracyChanged() only if the value changes
+ final int accuracy = mSensorAccuracies.get(handle);
+ if ((t.accuracy >= 0) && (accuracy != t.accuracy)) {
+ mSensorAccuracies.put(handle, t.accuracy);
+ mListener.onAccuracyChanged(t.sensor, t.accuracy);
}
mListener.onSensorChanged(t);
}
diff --git a/core/java/android/hardware/camera2/CameraAccessException.java b/core/java/android/hardware/camera2/CameraAccessException.java
index 1af575f..ca71e81 100644
--- a/core/java/android/hardware/camera2/CameraAccessException.java
+++ b/core/java/android/hardware/camera2/CameraAccessException.java
@@ -114,7 +114,10 @@ public class CameraAccessException extends AndroidException {
mReason = problem;
}
- private static String getDefaultMessage(int problem) {
+ /**
+ * @hide
+ */
+ public static String getDefaultMessage(int problem) {
switch (problem) {
case CAMERA_IN_USE:
return "The camera device is in use already";
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 83db056..4a89fe7 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -84,9 +84,8 @@ public final class CameraManager {
try {
CameraBinderDecorator.throwOnError(
CameraMetadataNative.nativeSetupGlobalVendorTagDescriptor());
- } catch(CameraRuntimeException e) {
- throw new IllegalStateException("Failed to setup camera vendor tags",
- e.asChecked());
+ } catch (CameraRuntimeException e) {
+ handleRecoverableSetupErrors(e, "Failed to set up vendor tags");
}
try {
@@ -426,6 +425,18 @@ public final class CameraManager {
return mDeviceIdList;
}
+ private void handleRecoverableSetupErrors(CameraRuntimeException e, String msg) {
+ int problem = e.getReason();
+ switch (problem) {
+ case CameraAccessException.CAMERA_DISCONNECTED:
+ String errorMsg = CameraAccessException.getDefaultMessage(problem);
+ Log.w(TAG, msg + ": " + errorMsg);
+ break;
+ default:
+ throw new IllegalStateException(msg, e.asChecked());
+ }
+ }
+
// TODO: this class needs unit tests
// TODO: extract class into top level
private class CameraServiceListener extends ICameraServiceListener.Stub {
diff --git a/core/java/android/os/FileBridge.java b/core/java/android/os/FileBridge.java
new file mode 100644
index 0000000..7f8bc9f
--- /dev/null
+++ b/core/java/android/os/FileBridge.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2014 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.os;
+
+import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.SOCK_STREAM;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import libcore.io.IoBridge;
+import libcore.io.IoUtils;
+import libcore.io.Memory;
+import libcore.io.Streams;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.SyncFailedException;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/**
+ * Simple bridge that allows file access across process boundaries without
+ * returning the underlying {@link FileDescriptor}. This is useful when the
+ * server side needs to strongly assert that a client side is completely
+ * hands-off.
+ *
+ * @hide
+ */
+public class FileBridge extends Thread {
+ private static final String TAG = "FileBridge";
+
+ // TODO: consider extending to support bidirectional IO
+
+ private static final int MSG_LENGTH = 8;
+
+ /** CMD_WRITE [len] [data] */
+ private static final int CMD_WRITE = 1;
+ /** CMD_FSYNC */
+ private static final int CMD_FSYNC = 2;
+
+ private FileDescriptor mTarget;
+
+ private final FileDescriptor mServer = new FileDescriptor();
+ private final FileDescriptor mClient = new FileDescriptor();
+
+ private volatile boolean mClosed;
+
+ public FileBridge() {
+ try {
+ Os.socketpair(AF_UNIX, SOCK_STREAM, 0, mServer, mClient);
+ } catch (ErrnoException e) {
+ throw new RuntimeException("Failed to create bridge");
+ }
+ }
+
+ public boolean isClosed() {
+ return mClosed;
+ }
+
+ public void setTargetFile(FileDescriptor target) {
+ mTarget = target;
+ }
+
+ public FileDescriptor getClientSocket() {
+ return mClient;
+ }
+
+ @Override
+ public void run() {
+ final byte[] temp = new byte[8192];
+ try {
+ while (IoBridge.read(mServer, temp, 0, MSG_LENGTH) == MSG_LENGTH) {
+ final int cmd = Memory.peekInt(temp, 0, ByteOrder.BIG_ENDIAN);
+
+ if (cmd == CMD_WRITE) {
+ // Shuttle data into local file
+ int len = Memory.peekInt(temp, 4, ByteOrder.BIG_ENDIAN);
+ while (len > 0) {
+ int n = IoBridge.read(mServer, temp, 0, Math.min(temp.length, len));
+ IoBridge.write(mTarget, temp, 0, n);
+ len -= n;
+ }
+
+ } else if (cmd == CMD_FSYNC) {
+ // Sync and echo back to confirm
+ Os.fsync(mTarget);
+ IoBridge.write(mServer, temp, 0, MSG_LENGTH);
+ }
+ }
+
+ // Client was closed; one last fsync
+ Os.fsync(mTarget);
+
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Failed during bridge: ", e);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed during bridge: ", e);
+ } finally {
+ IoUtils.closeQuietly(mTarget);
+ IoUtils.closeQuietly(mServer);
+ IoUtils.closeQuietly(mClient);
+ mClosed = true;
+ }
+ }
+
+ public static class FileBridgeOutputStream extends OutputStream {
+ private final FileDescriptor mClient;
+ private final byte[] mTemp = new byte[MSG_LENGTH];
+
+ public FileBridgeOutputStream(FileDescriptor client) {
+ mClient = client;
+ }
+
+ @Override
+ public void close() throws IOException {
+ IoBridge.closeAndSignalBlockedThreads(mClient);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ Memory.pokeInt(mTemp, 0, CMD_FSYNC, ByteOrder.BIG_ENDIAN);
+ IoBridge.write(mClient, mTemp, 0, MSG_LENGTH);
+
+ // Wait for server to ack
+ if (IoBridge.read(mClient, mTemp, 0, MSG_LENGTH) == MSG_LENGTH) {
+ if (Memory.peekInt(mTemp, 0, ByteOrder.BIG_ENDIAN) == CMD_FSYNC) {
+ return;
+ }
+ }
+
+ throw new SyncFailedException("Failed to fsync() across bridge");
+ }
+
+ @Override
+ public void write(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+ Arrays.checkOffsetAndCount(buffer.length, byteOffset, byteCount);
+ Memory.pokeInt(mTemp, 0, CMD_WRITE, ByteOrder.BIG_ENDIAN);
+ Memory.pokeInt(mTemp, 4, byteCount, ByteOrder.BIG_ENDIAN);
+ IoBridge.write(mClient, mTemp, 0, MSG_LENGTH);
+ IoBridge.write(mClient, buffer, byteOffset, byteCount);
+ }
+
+ @Override
+ public void write(int oneByte) throws IOException {
+ Streams.writeSingleByte(this, oneByte);
+ }
+ }
+}
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 57ed979..474192f 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -70,6 +70,8 @@ public final class Trace {
public static final long TRACE_TAG_DALVIK = 1L << 14;
/** @hide */
public static final long TRACE_TAG_RS = 1L << 15;
+ /** @hide */
+ public static final long TRACE_TAG_BIONIC = 1L << 16;
private static final long TRACE_TAG_NOT_READY = 1L << 63;
private static final int MAX_SECTION_NAME_LEN = 127;
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index cb0f142..c1d4d4c 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -74,7 +74,6 @@ public abstract class Vibrator {
* @param streamHint An {@link AudioManager} stream type corresponding to the vibration type.
* For example, specify {@link AudioManager#STREAM_ALARM} for alarm vibrations or
* {@link AudioManager#STREAM_RING} for vibrations associated with incoming calls.
- * @hide
*/
public void vibrate(long milliseconds, int streamHint) {
vibrate(Process.myUid(), mPackageName, milliseconds, streamHint);
@@ -126,7 +125,6 @@ public abstract class Vibrator {
* @param streamHint An {@link AudioManager} stream type corresponding to the vibration type.
* For example, specify {@link AudioManager#STREAM_ALARM} for alarm vibrations or
* {@link AudioManager#STREAM_RING} for vibrations associated with incoming calls.
- * @hide
*/
public void vibrate(long[] pattern, int repeat, int streamHint) {
vibrate(Process.myUid(), mPackageName, pattern, repeat, streamHint);
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index b53ea81..6db78f4 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -1152,8 +1152,6 @@ public final class ContactsContract {
* address book index, which is usually the first letter of the sort key.
* When this parameter is supplied, the row counts are returned in the
* cursor extras bundle.
- *
- * @hide
*/
public final static class ContactCounts {
@@ -1163,7 +1161,24 @@ public final class ContactsContract {
* first letter of the sort key. This parameter does not affect the main
* content of the cursor.
*
- * @hide
+ * <p>
+ * <pre>
+ * Example:
+ * Uri uri = Contacts.CONTENT_URI.buildUpon()
+ * .appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true")
+ * .build();
+ * Cursor cursor = getContentResolver().query(uri,
+ * new String[] {Contacts.DISPLAY_NAME},
+ * null, null, null);
+ * Bundle bundle = cursor.getExtras();
+ * if (bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES) &&
+ * bundle.containsKey(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS)) {
+ * String sections[] =
+ * bundle.getStringArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
+ * int counts[] = bundle.getIntArray(ContactCounts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
+ * }
+ * </pre>
+ * </p>
*/
public static final String ADDRESS_BOOK_INDEX_EXTRAS = "address_book_index_extras";
@@ -1171,8 +1186,6 @@ public final class ContactsContract {
* The array of address book index titles, which are returned in the
* same order as the data in the cursor.
* <p>TYPE: String[]</p>
- *
- * @hide
*/
public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "address_book_index_titles";
@@ -1180,8 +1193,6 @@ public final class ContactsContract {
* The array of group counts for the corresponding group. Contains the same number
* of elements as the EXTRA_ADDRESS_BOOK_INDEX_TITLES array.
* <p>TYPE: int[]</p>
- *
- * @hide
*/
public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "address_book_index_counts";
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e9ffc52..bec401e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4461,6 +4461,12 @@ public final class Settings {
INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF;
/**
+ * Whether the device should wake when the wake gesture sensor detects motion.
+ * @hide
+ */
+ public static final String WAKE_GESTURE_ENABLED = "wake_gesture_enabled";
+
+ /**
* The current night mode that has been selected by the user. Owned
* and controlled by UiModeManagerService. Constants are as per
* UiModeManager.
diff --git a/core/java/android/speech/tts/Markup.java b/core/java/android/speech/tts/Markup.java
new file mode 100644
index 0000000..c886e5d
--- /dev/null
+++ b/core/java/android/speech/tts/Markup.java
@@ -0,0 +1,537 @@
+package android.speech.tts;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class that provides markup to a synthesis request to control aspects of speech.
+ * <p>
+ * Markup itself is a feature agnostic data format; the {@link Utterance} class defines the currently
+ * available set of features and should be used to construct instances of the Markup class.
+ * </p>
+ * <p>
+ * A marked up sentence is a tree. Each node has a type, an optional plain text, a set of
+ * parameters, and a list of children.
+ * The <b>type</b> defines what it contains, e.g. "text", "date", "measure", etc. A Markup node
+ * can be either a part of sentence (often a leaf node), or node altering some property of its
+ * children (node with children). The top level node has to be of type "utterance" and its children
+ * are synthesized in order.
+ * The <b>plain text</b> is optional except for the top level node. If the synthesis engine does not
+ * support Markup at all, it should use the plain text of the top level node. If an engine does not
+ * recognize or support a node type, it will try to use the plain text of that node if provided. If
+ * the plain text is null, it will synthesize its children in order.
+ * <b>Parameters</b> are key-value pairs specific to each node type. In case of a date node the
+ * parameters may be for example "month: 7" and "day: 10".
+ * The <b>nested markups</b> are children and can for example be used to nest semiotic classes (a
+ * measure may have a node of type "decimal" as its child) or to modify some property of its
+ * children. See "plain text" on how they are processed if the parent of the children is unknown to
+ * the engine.
+ * <p>
+ */
+public final class Markup implements Parcelable {
+
+ private String mType;
+ private String mPlainText;
+
+ private Bundle mParameters = new Bundle();
+ private List<Markup> mNestedMarkups = new ArrayList<Markup>();
+
+ private static final String TYPE = "type";
+ private static final String PLAIN_TEXT = "plain_text";
+ private static final String MARKUP = "markup";
+
+ private static final String IDENTIFIER_REGEX = "([0-9a-z_]+)";
+ private static final Pattern legalIdentifierPattern = Pattern.compile(IDENTIFIER_REGEX);
+
+ /**
+ * Constructs an empty markup.
+ */
+ public Markup() {}
+
+ /**
+ * Constructs a markup of the given type.
+ */
+ public Markup(String type) {
+ setType(type);
+ }
+
+ /**
+ * Returns the type of this node; can be null.
+ */
+ public String getType() {
+ return mType;
+ }
+
+ /**
+ * Sets the type of this node. can be null. May only contain [0-9a-z_].
+ */
+ public void setType(String type) {
+ if (type != null) {
+ Matcher matcher = legalIdentifierPattern.matcher(type);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Type cannot be empty and may only contain " +
+ "0-9, a-z and underscores.");
+ }
+ }
+ mType = type;
+ }
+
+ /**
+ * Returns this node's plain text; can be null.
+ */
+ public String getPlainText() {
+ return mPlainText;
+ }
+
+ /**
+ * Sets this nodes's plain text; can be null.
+ */
+ public void setPlainText(String plainText) {
+ mPlainText = plainText;
+ }
+
+ /**
+ * Adds or modifies a parameter.
+ * @param key The key; may only contain [0-9a-z_] and cannot be "type" or "plain_text".
+ * @param value The value.
+ * @throws An {@link IllegalArgumentException} if the key is null or empty.
+ * @return this
+ */
+ public Markup setParameter(String key, String value) {
+ if (key == null || key.isEmpty()) {
+ throw new IllegalArgumentException("Key cannot be null or empty.");
+ }
+ if (key.equals("type")) {
+ throw new IllegalArgumentException("Key cannot be \"type\".");
+ }
+ if (key.equals("plain_text")) {
+ throw new IllegalArgumentException("Key cannot be \"plain_text\".");
+ }
+ Matcher matcher = legalIdentifierPattern.matcher(key);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Key may only contain 0-9, a-z and underscores.");
+ }
+
+ if (value != null) {
+ mParameters.putString(key, value);
+ } else {
+ removeParameter(key);
+ }
+ return this;
+ }
+
+ /**
+ * Removes the parameter with the given key
+ */
+ public void removeParameter(String key) {
+ mParameters.remove(key);
+ }
+
+ /**
+ * Returns the value of the parameter.
+ * @param key The parameter key.
+ * @return The value of the parameter or null if the parameter is not set.
+ */
+ public String getParameter(String key) {
+ return mParameters.getString(key);
+ }
+
+ /**
+ * Returns the number of parameters that have been set.
+ */
+ public int parametersSize() {
+ return mParameters.size();
+ }
+
+ /**
+ * Appends a child to the list of children
+ * @param markup The child.
+ * @return This instance.
+ * @throws {@link IllegalArgumentException} if markup is null.
+ */
+ public Markup addNestedMarkup(Markup markup) {
+ if (markup == null) {
+ throw new IllegalArgumentException("Nested markup cannot be null");
+ }
+ mNestedMarkups.add(markup);
+ return this;
+ }
+
+ /**
+ * Removes the given node from its children.
+ * @param markup The child to remove.
+ * @return True if this instance was modified by this operation, false otherwise.
+ */
+ public boolean removeNestedMarkup(Markup markup) {
+ return mNestedMarkups.remove(markup);
+ }
+
+ /**
+ * Returns the index'th child.
+ * @param i The index of the child.
+ * @return The child.
+ * @throws {@link IndexOutOfBoundsException} if i < 0 or i >= nestedMarkupSize()
+ */
+ public Markup getNestedMarkup(int i) {
+ return mNestedMarkups.get(i);
+ }
+
+
+ /**
+ * Returns the number of children.
+ */
+ public int nestedMarkupSize() {
+ return mNestedMarkups.size();
+ }
+
+ /**
+ * Returns a string representation of this Markup instance. Can be deserialized back to a Markup
+ * instance with markupFromString().
+ */
+ public String toString() {
+ StringBuilder out = new StringBuilder();
+ if (mType != null) {
+ out.append(TYPE + ": \"" + mType + "\"");
+ }
+ if (mPlainText != null) {
+ out.append(out.length() > 0 ? " " : "");
+ out.append(PLAIN_TEXT + ": \"" + escapeQuotedString(mPlainText) + "\"");
+ }
+ // Sort the parameters alphabetically by key so we have a stable output.
+ SortedMap<String, String> sortedMap = new TreeMap<String, String>();
+ for (String key : mParameters.keySet()) {
+ sortedMap.put(key, mParameters.getString(key));
+ }
+ for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
+ out.append(out.length() > 0 ? " " : "");
+ out.append(entry.getKey() + ": \"" + escapeQuotedString(entry.getValue()) + "\"");
+ }
+ for (Markup m : mNestedMarkups) {
+ out.append(out.length() > 0 ? " " : "");
+ String nestedStr = m.toString();
+ if (nestedStr.isEmpty()) {
+ out.append(MARKUP + " {}");
+ } else {
+ out.append(MARKUP + " { " + m.toString() + " }");
+ }
+ }
+ return out.toString();
+ }
+
+ /**
+ * Escapes backslashes and double quotes in the plain text and parameter values before this
+ * instance is written to a string.
+ * @param str The string to escape.
+ * @return The escaped string.
+ */
+ private static String escapeQuotedString(String str) {
+ StringBuilder out = new StringBuilder();
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ if (c == '"') {
+ out.append("\\\"");
+ } else if (str.charAt(i) == '\\') {
+ out.append("\\\\");
+ } else {
+ out.append(c);
+ }
+ }
+ return out.toString();
+ }
+
+ /**
+ * The reverse of the escape method, returning plain text and parameter values to their original
+ * form.
+ * @param str An escaped string.
+ * @return The unescaped string.
+ */
+ private static String unescapeQuotedString(String str) {
+ StringBuilder out = new StringBuilder();
+ for (int i = 0; i < str.length(); i++) {
+ char c = str.charAt(i);
+ if (c == '\\') {
+ i++;
+ if (i >= str.length()) {
+ throw new IllegalArgumentException("Unterminated escape sequence in string: " +
+ str);
+ }
+ c = str.charAt(i);
+ if (c == '\\') {
+ out.append("\\");
+ } else if (c == '"') {
+ out.append("\"");
+ } else {
+ throw new IllegalArgumentException("Unsupported escape sequence: \\" + c +
+ " in string " + str);
+ }
+ } else {
+ out.append(c);
+ }
+ }
+ return out.toString();
+ }
+
+ /**
+ * Returns true if the given string consists only of whitespace.
+ * @param str The string to check.
+ * @return True if the given string consists only of whitespace.
+ */
+ private static boolean isWhitespace(String str) {
+ return Pattern.matches("\\s*", str);
+ }
+
+ /**
+ * Parses the given string, and overrides the values of this instance with those contained
+ * in the given string.
+ * @param str The string to parse; can have superfluous whitespace.
+ * @return An empty string on success, else the remainder of the string that could not be
+ * parsed.
+ */
+ private String fromReadableString(String str) {
+ while (!isWhitespace(str)) {
+ String newStr = matchValue(str);
+ if (newStr == null) {
+ newStr = matchMarkup(str);
+
+ if (newStr == null) {
+ return str;
+ }
+ }
+ str = newStr;
+ }
+ return "";
+ }
+
+ // Matches: key : "value"
+ // where key is an identifier and value can contain escaped quotes
+ // there may be superflouous whitespace
+ // The value string may contain quotes and backslashes.
+ private static final String OPTIONAL_WHITESPACE = "\\s*";
+ private static final String VALUE_REGEX = "((\\\\.|[^\\\"])*)";
+ private static final String KEY_VALUE_REGEX =
+ "\\A" + OPTIONAL_WHITESPACE + // start of string
+ IDENTIFIER_REGEX + OPTIONAL_WHITESPACE + ":" + OPTIONAL_WHITESPACE + // key:
+ "\"" + VALUE_REGEX + "\""; // "value"
+ private static final Pattern KEY_VALUE_PATTERN = Pattern.compile(KEY_VALUE_REGEX);
+
+ /**
+ * Tries to match a key-value pair at the start of the string. If found, add that as a parameter
+ * of this instance.
+ * @param str The string to parse.
+ * @return The remainder of the string without the parsed key-value pair on success, else null.
+ */
+ private String matchValue(String str) {
+ // Matches: key: "value"
+ Matcher matcher = KEY_VALUE_PATTERN.matcher(str);
+ if (!matcher.find()) {
+ return null;
+ }
+ String key = matcher.group(1);
+ String value = matcher.group(2);
+
+ if (key == null || value == null) {
+ return null;
+ }
+ String unescapedValue = unescapeQuotedString(value);
+ if (key.equals(TYPE)) {
+ this.mType = unescapedValue;
+ } else if (key.equals(PLAIN_TEXT)) {
+ this.mPlainText = unescapedValue;
+ } else {
+ setParameter(key, unescapedValue);
+ }
+
+ return str.substring(matcher.group(0).length());
+ }
+
+ // matches 'markup {'
+ private static final Pattern OPEN_MARKUP_PATTERN =
+ Pattern.compile("\\A" + OPTIONAL_WHITESPACE + MARKUP + OPTIONAL_WHITESPACE + "\\{");
+ // matches '}'
+ private static final Pattern CLOSE_MARKUP_PATTERN =
+ Pattern.compile("\\A" + OPTIONAL_WHITESPACE + "\\}");
+
+ /**
+ * Tries to parse a Markup specification from the start of the string. If so, add that markup to
+ * the list of nested Markup's of this instance.
+ * @param str The string to parse.
+ * @return The remainder of the string without the parsed Markup on success, else null.
+ */
+ private String matchMarkup(String str) {
+ // find and strip "markup {"
+ Matcher matcher = OPEN_MARKUP_PATTERN.matcher(str);
+
+ if (!matcher.find()) {
+ return null;
+ }
+ String strRemainder = str.substring(matcher.group(0).length());
+ // parse and strip markup contents
+ Markup nestedMarkup = new Markup();
+ strRemainder = nestedMarkup.fromReadableString(strRemainder);
+
+ // find and strip "}"
+ Matcher matcherClose = CLOSE_MARKUP_PATTERN.matcher(strRemainder);
+ if (!matcherClose.find()) {
+ return null;
+ }
+ strRemainder = strRemainder.substring(matcherClose.group(0).length());
+
+ // Everything parsed, add markup
+ this.addNestedMarkup(nestedMarkup);
+
+ // Return remainder
+ return strRemainder;
+ }
+
+ /**
+ * Returns a Markup instance from the string representation generated by toString().
+ * @param string The string representation generated by toString().
+ * @return The new Markup instance.
+ * @throws {@link IllegalArgumentException} if the input cannot be correctly parsed.
+ */
+ public static Markup markupFromString(String string) throws IllegalArgumentException {
+ Markup m = new Markup();
+ if (m.fromReadableString(string).isEmpty()) {
+ return m;
+ } else {
+ throw new IllegalArgumentException("Cannot parse input to Markup");
+ }
+ }
+
+ /**
+ * Compares the specified object with this Markup for equality.
+ * @return True if the given object is a Markup instance with the same type, plain text,
+ * parameters and the nested markups are also equal to each other and in the same order.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if ( this == o ) return true;
+ if ( !(o instanceof Markup) ) return false;
+ Markup m = (Markup) o;
+
+ if (nestedMarkupSize() != this.nestedMarkupSize()) {
+ return false;
+ }
+
+ if (!(mType == null ? m.mType == null : mType.equals(m.mType))) {
+ return false;
+ }
+ if (!(mPlainText == null ? m.mPlainText == null : mPlainText.equals(m.mPlainText))) {
+ return false;
+ }
+ if (!equalBundles(mParameters, m.mParameters)) {
+ return false;
+ }
+
+ for (int i = 0; i < this.nestedMarkupSize(); i++) {
+ if (!mNestedMarkups.get(i).equals(m.mNestedMarkups.get(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if two bundles are equal to each other. Used by equals(o).
+ */
+ private boolean equalBundles(Bundle one, Bundle two) {
+ if (one == null || two == null) {
+ return false;
+ }
+
+ if(one.size() != two.size()) {
+ return false;
+ }
+
+ Set<String> valuesOne = one.keySet();
+ for(String key : valuesOne) {
+ Object valueOne = one.get(key);
+ Object valueTwo = two.get(key);
+ if (valueOne instanceof Bundle && valueTwo instanceof Bundle &&
+ !equalBundles((Bundle) valueOne, (Bundle) valueTwo)) {
+ return false;
+ } else if (valueOne == null) {
+ if (valueTwo != null || !two.containsKey(key)) {
+ return false;
+ }
+ } else if(!valueOne.equals(valueTwo)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns an unmodifiable list of the children.
+ * @return An unmodifiable list of children that throws an {@link UnsupportedOperationException}
+ * if an attempt is made to modify it
+ */
+ public List<Markup> getNestedMarkups() {
+ return Collections.unmodifiableList(mNestedMarkups);
+ }
+
+ /**
+ * @hide
+ */
+ public Markup(Parcel in) {
+ mType = in.readString();
+ mPlainText = in.readString();
+ mParameters = in.readBundle();
+ in.readList(mNestedMarkups, Markup.class.getClassLoader());
+ }
+
+ /**
+ * Creates a deep copy of the given markup.
+ */
+ public Markup(Markup markup) {
+ mType = markup.mType;
+ mPlainText = markup.mPlainText;
+ mParameters = markup.mParameters;
+ for (Markup nested : markup.getNestedMarkups()) {
+ addNestedMarkup(new Markup(nested));
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @hide
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mType);
+ dest.writeString(mPlainText);
+ dest.writeBundle(mParameters);
+ dest.writeList(mNestedMarkups);
+ }
+
+ /**
+ * @hide
+ */
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public Markup createFromParcel(Parcel in) {
+ return new Markup(in);
+ }
+
+ public Markup[] newArray(int size) {
+ return new Markup[size];
+ }
+ };
+}
+
diff --git a/core/java/android/speech/tts/SynthesisRequestV2.java b/core/java/android/speech/tts/SynthesisRequestV2.java
index a1da49c..130e3f9 100644
--- a/core/java/android/speech/tts/SynthesisRequestV2.java
+++ b/core/java/android/speech/tts/SynthesisRequestV2.java
@@ -4,11 +4,12 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.speech.tts.TextToSpeechClient.UtteranceId;
+import android.util.Log;
/**
* Service-side representation of a synthesis request from a V2 API client. Contains:
* <ul>
- * <li>The utterance to synthesize</li>
+ * <li>The markup object to synthesize containing the utterance.</li>
* <li>The id of the utterance (String, result of {@link UtteranceId#toUniqueString()}</li>
* <li>The synthesis voice name (String, result of {@link VoiceInfo#getName()})</li>
* <li>Voice parameters (Bundle of parameters)</li>
@@ -16,8 +17,11 @@ import android.speech.tts.TextToSpeechClient.UtteranceId;
* </ul>
*/
public final class SynthesisRequestV2 implements Parcelable {
- /** Synthesis utterance. */
- private final String mText;
+
+ private static final String TAG = "SynthesisRequestV2";
+
+ /** Synthesis markup */
+ private final Markup mMarkup;
/** Synthesis id. */
private final String mUtteranceId;
@@ -34,9 +38,9 @@ public final class SynthesisRequestV2 implements Parcelable {
/**
* Constructor for test purposes.
*/
- public SynthesisRequestV2(String text, String utteranceId, String voiceName,
+ public SynthesisRequestV2(Markup markup, String utteranceId, String voiceName,
Bundle voiceParams, Bundle audioParams) {
- this.mText = text;
+ this.mMarkup = markup;
this.mUtteranceId = utteranceId;
this.mVoiceName = voiceName;
this.mVoiceParams = voiceParams;
@@ -49,15 +53,18 @@ public final class SynthesisRequestV2 implements Parcelable {
* @hide
*/
public SynthesisRequestV2(Parcel in) {
- this.mText = in.readString();
+ this.mMarkup = (Markup) in.readValue(Markup.class.getClassLoader());
this.mUtteranceId = in.readString();
this.mVoiceName = in.readString();
this.mVoiceParams = in.readBundle();
this.mAudioParams = in.readBundle();
}
- SynthesisRequestV2(String text, String utteranceId, RequestConfig rconfig) {
- this.mText = text;
+ /**
+ * Constructor to request the synthesis of a sentence.
+ */
+ SynthesisRequestV2(Markup markup, String utteranceId, RequestConfig rconfig) {
+ this.mMarkup = markup;
this.mUtteranceId = utteranceId;
this.mVoiceName = rconfig.getVoice().getName();
this.mVoiceParams = rconfig.getVoiceParams();
@@ -71,7 +78,7 @@ public final class SynthesisRequestV2 implements Parcelable {
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(mText);
+ dest.writeValue(mMarkup);
dest.writeString(mUtteranceId);
dest.writeString(mVoiceName);
dest.writeBundle(mVoiceParams);
@@ -82,7 +89,18 @@ public final class SynthesisRequestV2 implements Parcelable {
* @return the text which should be synthesized.
*/
public String getText() {
- return mText;
+ if (mMarkup.getPlainText() == null) {
+ Log.e(TAG, "Plaintext of markup is null.");
+ return "";
+ }
+ return mMarkup.getPlainText();
+ }
+
+ /**
+ * @return the markup which should be synthesized.
+ */
+ public Markup getMarkup() {
+ return mMarkup;
}
/**
diff --git a/core/java/android/speech/tts/TextToSpeechClient.java b/core/java/android/speech/tts/TextToSpeechClient.java
index 85f702b..e17b498 100644
--- a/core/java/android/speech/tts/TextToSpeechClient.java
+++ b/core/java/android/speech/tts/TextToSpeechClient.java
@@ -512,7 +512,6 @@ public class TextToSpeechClient {
}
}
-
/**
* Connects the client to TTS service. This method returns immediately, and connects to the
* service in the background.
@@ -876,7 +875,7 @@ public class TextToSpeechClient {
private static final String ACTION_QUEUE_SPEAK_NAME = "queueSpeak";
/**
- * Speaks the string using the specified queuing strategy using current
+ * Speaks the string using the specified queuing strategy and the current
* voice. This method is asynchronous, i.e. the method just adds the request
* to the queue of TTS requests and then returns. The synthesis might not
* have finished (or even started!) at the time when this method returns.
@@ -887,12 +886,35 @@ public class TextToSpeechClient {
* in {@link RequestCallbacks}.
* @param config Synthesis request configuration. Can't be null. Has to contain a
* voice.
- * @param callbacks Synthesis request callbacks. If null, default request
+ * @param callbacks Synthesis request callbacks. If null, the default request
* callbacks object will be used.
*/
public void queueSpeak(final String utterance, final UtteranceId utteranceId,
final RequestConfig config,
final RequestCallbacks callbacks) {
+ queueSpeak(createMarkupFromString(utterance), utteranceId, config, callbacks);
+ }
+
+ /**
+ * Speaks the {@link Markup} (which can be constructed with {@link Utterance}) using
+ * the specified queuing strategy and the current voice. This method is
+ * asynchronous, i.e. the method just adds the request to the queue of TTS
+ * requests and then returns. The synthesis might not have finished (or even
+ * started!) at the time when this method returns.
+ *
+ * @param markup The Markup to be spoken. The written equivalent of the spoken
+ * text should be no longer than 1000 characters.
+ * @param utteranceId Unique identificator used to track the synthesis progress
+ * in {@link RequestCallbacks}.
+ * @param config Synthesis request configuration. Can't be null. Has to contain a
+ * voice.
+ * @param callbacks Synthesis request callbacks. If null, the default request
+ * callbacks object will be used.
+ */
+ public void queueSpeak(final Markup markup,
+ final UtteranceId utteranceId,
+ final RequestConfig config,
+ final RequestCallbacks callbacks) {
runAction(new Action(ACTION_QUEUE_SPEAK_NAME) {
@Override
public void run(ITextToSpeechService service) throws RemoteException {
@@ -908,7 +930,7 @@ public class TextToSpeechClient {
int queueResult = service.speakV2(
getCallerIdentity(),
- new SynthesisRequestV2(utterance, utteranceId.toUniqueString(), config));
+ new SynthesisRequestV2(markup, utteranceId.toUniqueString(), config));
if (queueResult != Status.SUCCESS) {
removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
}
@@ -931,12 +953,37 @@ public class TextToSpeechClient {
* @param outputFile File to write the generated audio data to.
* @param config Synthesis request configuration. Can't be null. Have to contain a
* voice.
- * @param callbacks Synthesis request callbacks. If null, default request
+ * @param callbacks Synthesis request callbacks. If null, the default request
* callbacks object will be used.
*/
public void queueSynthesizeToFile(final String utterance, final UtteranceId utteranceId,
final File outputFile, final RequestConfig config,
final RequestCallbacks callbacks) {
+ queueSynthesizeToFile(createMarkupFromString(utterance), utteranceId, outputFile, config, callbacks);
+ }
+
+ /**
+ * Synthesizes the given {@link Markup} (can be constructed with {@link Utterance})
+ * to a file using the specified parameters. This method is asynchronous, i.e. the
+ * method just adds the request to the queue of TTS requests and then returns. The
+ * synthesis might not have finished (or even started!) at the time when this method
+ * returns.
+ *
+ * @param markup The Markup that should be synthesized. The written equivalent of
+ * the spoken text should be no longer than 1000 characters.
+ * @param utteranceId Unique identificator used to track the synthesis progress
+ * in {@link RequestCallbacks}.
+ * @param outputFile File to write the generated audio data to.
+ * @param config Synthesis request configuration. Can't be null. Have to contain a
+ * voice.
+ * @param callbacks Synthesis request callbacks. If null, the default request
+ * callbacks object will be used.
+ */
+ public void queueSynthesizeToFile(
+ final Markup markup,
+ final UtteranceId utteranceId,
+ final File outputFile, final RequestConfig config,
+ final RequestCallbacks callbacks) {
runAction(new Action(ACTION_QUEUE_SYNTHESIZE_TO_FILE) {
@Override
public void run(ITextToSpeechService service) throws RemoteException {
@@ -964,8 +1011,7 @@ public class TextToSpeechClient {
int queueResult = service.synthesizeToFileDescriptorV2(getCallerIdentity(),
fileDescriptor,
- new SynthesisRequestV2(utterance, utteranceId.toUniqueString(),
- config));
+ new SynthesisRequestV2(markup, utteranceId.toUniqueString(), config));
fileDescriptor.close();
if (queueResult != Status.SUCCESS) {
removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
@@ -981,6 +1027,13 @@ public class TextToSpeechClient {
});
}
+ private static Markup createMarkupFromString(String str) {
+ return new Utterance()
+ .append(new Utterance.TtsText(str))
+ .setNoWarningOnFallback(true)
+ .createMarkup();
+ }
+
private static final String ACTION_QUEUE_SILENCE_NAME = "queueSilence";
/**
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 6b899d9..14a4024 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -352,6 +352,12 @@ public abstract class TextToSpeechService extends Service {
params.putString(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS, "true");
}
+ String noWarning = request.getMarkup().getParameter(Utterance.KEY_NO_WARNING_ON_FALLBACK);
+ if (noWarning == null || noWarning.equals("false")) {
+ Log.w("TextToSpeechService", "The synthesis engine does not support Markup, falling " +
+ "back to the given plain text.");
+ }
+
// Build V1 request
SynthesisRequest requestV1 = new SynthesisRequest(request.getText(), params);
Locale locale = selectedVoice.getLocale();
@@ -856,14 +862,53 @@ public abstract class TextToSpeechService extends Service {
}
}
+ /**
+ * Estimate of the character count equivalent of a Markup instance. Calculated
+ * by summing the characters of all Markups of type "text". Each other node
+ * is counted as a single character, as the character count of other nodes
+ * is non-trivial to calculate and we don't want to accept arbitrarily large
+ * requests.
+ */
+ private int estimateSynthesisLengthFromMarkup(Markup m) {
+ int size = 0;
+ if (m.getType() != null &&
+ m.getType().equals("text") &&
+ m.getParameter("text") != null) {
+ size += m.getParameter("text").length();
+ } else if (m.getType() == null ||
+ !m.getType().equals("utterance")) {
+ size += 1;
+ }
+ for (Markup nested : m.getNestedMarkups()) {
+ size += estimateSynthesisLengthFromMarkup(nested);
+ }
+ return size;
+ }
+
@Override
public boolean isValid() {
- if (mSynthesisRequest.getText() == null) {
- Log.e(TAG, "null synthesis text");
+ if (mSynthesisRequest.getMarkup() == null) {
+ Log.e(TAG, "No markup in request.");
return false;
}
- if (mSynthesisRequest.getText().length() >= TextToSpeech.getMaxSpeechInputLength()) {
- Log.w(TAG, "Text too long: " + mSynthesisRequest.getText().length() + " chars");
+ String type = mSynthesisRequest.getMarkup().getType();
+ if (type == null) {
+ Log.w(TAG, "Top level markup node should have type \"utterance\", not null");
+ return false;
+ } else if (!type.equals("utterance")) {
+ Log.w(TAG, "Top level markup node should have type \"utterance\" instead of " +
+ "\"" + type + "\"");
+ return false;
+ }
+
+ int estimate = estimateSynthesisLengthFromMarkup(mSynthesisRequest.getMarkup());
+ if (estimate >= TextToSpeech.getMaxSpeechInputLength()) {
+ Log.w(TAG, "Text too long: estimated size of text was " + estimate + " chars.");
+ return false;
+ }
+
+ if (estimate <= 0) {
+ Log.e(TAG, "null synthesis text");
return false;
}
diff --git a/core/java/android/speech/tts/Utterance.java b/core/java/android/speech/tts/Utterance.java
new file mode 100644
index 0000000..0a29283
--- /dev/null
+++ b/core/java/android/speech/tts/Utterance.java
@@ -0,0 +1,595 @@
+package android.speech.tts;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class acts as a builder for {@link Markup} instances.
+ * <p>
+ * Each Utterance consists of a list of the semiotic classes ({@link Utterance.TtsCardinal} and
+ * {@link Utterance.TtsText}).
+ * <p>Each semiotic class can be supplied with morphosyntactic features
+ * (gender, animacy, multiplicity and case), it is up to the synthesis engine to use this
+ * information during synthesis.
+ * Examples where morphosyntactic features matter:
+ * <ul>
+ * <li>In French, the number one is verbalized differently based on the gender of the noun
+ * it modifies. "un homme" (one man) versus "une femme" (one woman).
+ * <li>In German the grammatical case (accusative, locative, etc) needs to be included to be
+ * verbalize correctly. In German you'd have the sentence "Sie haben 1 kilometer vor Ihnen" (You
+ * have 1 kilometer ahead of you), "1" in this case needs to become inflected to the accusative
+ * form ("einen") instead of the nominative form "ein".
+ * </p>
+ * <p>
+ * Utterance usage example:
+ * Markup m1 = new Utterance().append("The Eiffel Tower is")
+ * .append(new TtsCardinal(324))
+ * .append("meters tall.");
+ * Markup m2 = new Utterance().append("Sie haben")
+ * .append(new TtsCardinal(1).setGender(Utterance.GENDER_MALE)
+ * .append("Tag frei.");
+ * </p>
+ */
+public class Utterance {
+
+ /***
+ * Toplevel type of markup representation.
+ */
+ public static final String TYPE_UTTERANCE = "utterance";
+ /***
+ * The no_warning_on_fallback parameter can be set to "false" or "true", true indicating that
+ * no warning will be given when the synthesizer does not support Markup. This is used when
+ * the user only provides a string to the API instead of a markup.
+ */
+ public static final String KEY_NO_WARNING_ON_FALLBACK = "no_warning_on_fallback";
+
+ // Gender.
+ public final static int GENDER_UNKNOWN = 0;
+ public final static int GENDER_NEUTRAL = 1;
+ public final static int GENDER_MALE = 2;
+ public final static int GENDER_FEMALE = 3;
+
+ // Animacy.
+ public final static int ANIMACY_UNKNOWN = 0;
+ public final static int ANIMACY_ANIMATE = 1;
+ public final static int ANIMACY_INANIMATE = 2;
+
+ // Multiplicity.
+ public final static int MULTIPLICITY_UNKNOWN = 0;
+ public final static int MULTIPLICITY_SINGLE = 1;
+ public final static int MULTIPLICITY_DUAL = 2;
+ public final static int MULTIPLICITY_PLURAL = 3;
+
+ // Case.
+ public final static int CASE_UNKNOWN = 0;
+ public final static int CASE_NOMINATIVE = 1;
+ public final static int CASE_ACCUSATIVE = 2;
+ public final static int CASE_DATIVE = 3;
+ public final static int CASE_ABLATIVE = 4;
+ public final static int CASE_GENITIVE = 5;
+ public final static int CASE_VOCATIVE = 6;
+ public final static int CASE_LOCATIVE = 7;
+ public final static int CASE_INSTRUMENTAL = 8;
+
+ private List<AbstractTts<? extends AbstractTts<?>>> says =
+ new ArrayList<AbstractTts<? extends AbstractTts<?>>>();
+ Boolean mNoWarningOnFallback = null;
+
+ /**
+ * Objects deriving from this class can be appended to a Utterance. This class uses generics
+ * so method from this class can return instances of its child classes, resulting in a better
+ * API (CRTP pattern).
+ */
+ public static abstract class AbstractTts<C extends AbstractTts<C>> {
+
+ protected Markup mMarkup = new Markup();
+
+ /**
+ * Empty constructor.
+ */
+ protected AbstractTts() {
+ }
+
+ /**
+ * Construct with Markup.
+ * @param markup
+ */
+ protected AbstractTts(Markup markup) {
+ mMarkup = markup;
+ }
+
+ /**
+ * Returns the type of this class, e.g. "cardinal" or "measure".
+ * @return The type.
+ */
+ public String getType() {
+ return mMarkup.getType();
+ }
+
+ /**
+ * A fallback plain text can be provided, in case the engine does not support this class
+ * type, or even Markup altogether.
+ * @param plainText A string with the plain text.
+ * @return This instance.
+ */
+ @SuppressWarnings("unchecked")
+ public C setPlainText(String plainText) {
+ mMarkup.setPlainText(plainText);
+ return (C) this;
+ }
+
+ /**
+ * Returns the plain text (fallback) string.
+ * @return Plain text string or null if not set.
+ */
+ public String getPlainText() {
+ return mMarkup.getPlainText();
+ }
+
+ /**
+ * Populates the plainText if not set and builds a Markup instance.
+ * @return The Markup object describing this instance.
+ */
+ public Markup getMarkup() {
+ return new Markup(mMarkup);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected C setParameter(String key, String value) {
+ mMarkup.setParameter(key, value);
+ return (C) this;
+ }
+
+ protected String getParameter(String key) {
+ return mMarkup.getParameter(key);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected C removeParameter(String key) {
+ mMarkup.removeParameter(key);
+ return (C) this;
+ }
+
+ /**
+ * Returns a string representation of this instance, can be deserialized to an equal
+ * Utterance instance.
+ */
+ public String toString() {
+ return mMarkup.toString();
+ }
+
+ /**
+ * Returns a generated plain text alternative for this instance if this instance isn't
+ * better representated by the list of it's children.
+ * @return Best effort plain text representation of this instance, can be null.
+ */
+ public String generatePlainText() {
+ return null;
+ }
+ }
+
+ public static abstract class AbstractTtsSemioticClass<C extends AbstractTtsSemioticClass<C>>
+ extends AbstractTts<C> {
+ // Keys.
+ private static final String KEY_GENDER = "gender";
+ private static final String KEY_ANIMACY = "animacy";
+ private static final String KEY_MULTIPLICITY = "multiplicity";
+ private static final String KEY_CASE = "case";
+
+ protected AbstractTtsSemioticClass() {
+ super();
+ }
+
+ protected AbstractTtsSemioticClass(Markup markup) {
+ super(markup);
+ }
+
+ @SuppressWarnings("unchecked")
+ public C setGender(int gender) {
+ if (gender < 0 || gender > 3) {
+ throw new IllegalArgumentException("Only four types of gender can be set: " +
+ "unknown, neutral, maculine and female.");
+ }
+ if (gender != GENDER_UNKNOWN) {
+ setParameter(KEY_GENDER, String.valueOf(gender));
+ } else {
+ setParameter(KEY_GENDER, null);
+ }
+ return (C) this;
+ }
+
+ public int getGender() {
+ String gender = mMarkup.getParameter(KEY_GENDER);
+ return gender != null ? Integer.valueOf(gender) : GENDER_UNKNOWN;
+ }
+
+ @SuppressWarnings("unchecked")
+ public C setAnimacy(int animacy) {
+ if (animacy < 0 || animacy > 2) {
+ throw new IllegalArgumentException(
+ "Only two types of animacy can be set: unknown, animate and inanimate");
+ }
+ if (animacy != ANIMACY_UNKNOWN) {
+ setParameter(KEY_ANIMACY, String.valueOf(animacy));
+ } else {
+ setParameter(KEY_ANIMACY, null);
+ }
+ return (C) this;
+ }
+
+ public int getAnimacy() {
+ String animacy = getParameter(KEY_ANIMACY);
+ return animacy != null ? Integer.valueOf(animacy) : ANIMACY_UNKNOWN;
+ }
+
+ @SuppressWarnings("unchecked")
+ public C setMultiplicity(int multiplicity) {
+ if (multiplicity < 0 || multiplicity > 3) {
+ throw new IllegalArgumentException(
+ "Only four types of multiplicity can be set: unknown, single, dual and " +
+ "plural.");
+ }
+ if (multiplicity != MULTIPLICITY_UNKNOWN) {
+ setParameter(KEY_MULTIPLICITY, String.valueOf(multiplicity));
+ } else {
+ setParameter(KEY_MULTIPLICITY, null);
+ }
+ return (C) this;
+ }
+
+ public int getMultiplicity() {
+ String multiplicity = mMarkup.getParameter(KEY_MULTIPLICITY);
+ return multiplicity != null ? Integer.valueOf(multiplicity) : MULTIPLICITY_UNKNOWN;
+ }
+
+ @SuppressWarnings("unchecked")
+ public C setCase(int grammaticalCase) {
+ if (grammaticalCase < 0 || grammaticalCase > 8) {
+ throw new IllegalArgumentException(
+ "Only nine types of grammatical case can be set.");
+ }
+ if (grammaticalCase != CASE_UNKNOWN) {
+ setParameter(KEY_CASE, String.valueOf(grammaticalCase));
+ } else {
+ setParameter(KEY_CASE, null);
+ }
+ return (C) this;
+ }
+
+ public int getCase() {
+ String grammaticalCase = mMarkup.getParameter(KEY_CASE);
+ return grammaticalCase != null ? Integer.valueOf(grammaticalCase) : CASE_UNKNOWN;
+ }
+ }
+
+ /**
+ * Class that contains regular text, synthesis engine pronounces it using its regular pipeline.
+ * Parameters:
+ * <ul>
+ * <li>Text: the text to synthesize</li>
+ * </ul>
+ */
+ public static class TtsText extends AbstractTtsSemioticClass<TtsText> {
+
+ // The type of this node.
+ protected static final String TYPE_TEXT = "text";
+ // The text parameter stores the text to be synthesized.
+ private static final String KEY_TEXT = "text";
+
+ /**
+ * Default constructor.
+ */
+ public TtsText() {
+ mMarkup.setType(TYPE_TEXT);
+ }
+
+ /**
+ * Constructor that sets the text to be synthesized.
+ * @param text The text to be synthesized.
+ */
+ public TtsText(String text) {
+ this();
+ setText(text);
+ }
+
+ /**
+ * Constructs a TtsText with the values of the Markup, does not check if the given Markup is
+ * of the right type.
+ */
+ private TtsText(Markup markup) {
+ super(markup);
+ }
+
+ /**
+ * Sets the text to be synthesized.
+ * @return This instance.
+ */
+ public TtsText setText(String text) {
+ setParameter(KEY_TEXT, text);
+ return this;
+ }
+
+ /**
+ * Returns the text to be synthesized.
+ * @return This instance.
+ */
+ public String getText() {
+ return getParameter(KEY_TEXT);
+ }
+
+ /**
+ * Generates a best effort plain text, in this case simply the text.
+ */
+ @Override
+ public String generatePlainText() {
+ return getText();
+ }
+ }
+
+ /**
+ * Contains a cardinal.
+ * Parameters:
+ * <ul>
+ * <li>integer: the integer to synthesize</li>
+ * </ul>
+ */
+ public static class TtsCardinal extends AbstractTtsSemioticClass<TtsCardinal> {
+
+ // The type of this node.
+ protected static final String TYPE_CARDINAL = "cardinal";
+ // The parameter integer stores the integer to synthesize.
+ private static final String KEY_INTEGER = "integer";
+
+ /**
+ * Default constructor.
+ */
+ public TtsCardinal() {
+ mMarkup.setType(TYPE_CARDINAL);
+ }
+
+ /**
+ * Constructor that sets the integer to be synthesized.
+ */
+ public TtsCardinal(int integer) {
+ this();
+ setInteger(integer);
+ }
+
+ /**
+ * Constructor that sets the integer to be synthesized.
+ */
+ public TtsCardinal(String integer) {
+ this();
+ setInteger(integer);
+ }
+
+ /**
+ * Constructs a TtsText with the values of the Markup.
+ * Does not check if the given Markup is of the right type.
+ */
+ private TtsCardinal(Markup markup) {
+ super(markup);
+ }
+
+ /**
+ * Sets the integer.
+ * @return This instance.
+ */
+ public TtsCardinal setInteger(int integer) {
+ return setInteger(String.valueOf(integer));
+ }
+
+ /**
+ * Sets the integer.
+ * @param integer A non-empty string of digits with an optional '-' in front.
+ * @return This instance.
+ */
+ public TtsCardinal setInteger(String integer) {
+ if (!integer.matches("-?\\d+")) {
+ throw new IllegalArgumentException("Expected a cardinal: \"" + integer + "\"");
+ }
+ setParameter(KEY_INTEGER, integer);
+ return this;
+ }
+
+ /**
+ * Returns the integer parameter.
+ */
+ public String getInteger() {
+ return getParameter(KEY_INTEGER);
+ }
+
+ /**
+ * Generates a best effort plain text, in this case simply the integer.
+ */
+ @Override
+ public String generatePlainText() {
+ return getInteger();
+ }
+ }
+
+ /**
+ * Default constructor.
+ */
+ public Utterance() {}
+
+ /**
+ * Returns the plain text of a given Markup if it was set; if it's not set, recursively call the
+ * this same method on its children.
+ */
+ private String constructPlainText(Markup m) {
+ StringBuilder plainText = new StringBuilder();
+ if (m.getPlainText() != null) {
+ plainText.append(m.getPlainText());
+ } else {
+ for (Markup nestedMarkup : m.getNestedMarkups()) {
+ String nestedPlainText = constructPlainText(nestedMarkup);
+ if (!nestedPlainText.isEmpty()) {
+ if (plainText.length() != 0) {
+ plainText.append(" ");
+ }
+ plainText.append(nestedPlainText);
+ }
+ }
+ }
+ return plainText.toString();
+ }
+
+ /**
+ * Creates a Markup instance with auto generated plain texts for the relevant nodes, in case the
+ * user has not provided one already.
+ * @return A Markup instance representing this utterance.
+ */
+ public Markup createMarkup() {
+ Markup markup = new Markup(TYPE_UTTERANCE);
+ StringBuilder plainText = new StringBuilder();
+ for (AbstractTts<? extends AbstractTts<?>> say : says) {
+ // Get a copy of this markup, and generate a plaintext for it if is not set.
+ Markup sayMarkup = say.getMarkup();
+ if (sayMarkup.getPlainText() == null) {
+ sayMarkup.setPlainText(say.generatePlainText());
+ }
+ if (plainText.length() != 0) {
+ plainText.append(" ");
+ }
+ plainText.append(constructPlainText(sayMarkup));
+ markup.addNestedMarkup(sayMarkup);
+ }
+ if (mNoWarningOnFallback != null) {
+ markup.setParameter(KEY_NO_WARNING_ON_FALLBACK,
+ mNoWarningOnFallback ? "true" : "false");
+ }
+ markup.setPlainText(plainText.toString());
+ return markup;
+ }
+
+ /**
+ * Appends an element to this Utterance instance.
+ * @return this instance
+ */
+ public Utterance append(AbstractTts<? extends AbstractTts<?>> say) {
+ says.add(say);
+ return this;
+ }
+
+ private Utterance append(Markup markup) {
+ if (markup.getType().equals(TtsText.TYPE_TEXT)) {
+ append(new TtsText(markup));
+ } else if (markup.getType().equals(TtsCardinal.TYPE_CARDINAL)) {
+ append(new TtsCardinal(markup));
+ } else {
+ // Unknown node, a class we don't know about.
+ if (markup.getPlainText() != null) {
+ append(new TtsText(markup.getPlainText()));
+ } else {
+ // No plainText specified; add its children
+ // seperately. In case of a new prosody node,
+ // we would still verbalize it correctly.
+ for (Markup nested : markup.getNestedMarkups()) {
+ append(nested);
+ }
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Returns a string representation of this Utterance instance. Can be deserialized back to an
+ * Utterance instance with utteranceFromString(). Can be used to store utterances to be used
+ * at a later time.
+ */
+ public String toString() {
+ String out = "type: \"" + TYPE_UTTERANCE + "\"";
+ if (mNoWarningOnFallback != null) {
+ out += " no_warning_on_fallback: \"" + (mNoWarningOnFallback ? "true" : "false") + "\"";
+ }
+ for (AbstractTts<? extends AbstractTts<?>> say : says) {
+ out += " markup { " + say.getMarkup().toString() + " }";
+ }
+ return out;
+ }
+
+ /**
+ * Returns an Utterance instance from the string representation generated by toString().
+ * @param string The string representation generated by toString().
+ * @return The new Utterance instance.
+ * @throws {@link IllegalArgumentException} if the input cannot be correctly parsed.
+ */
+ static public Utterance utteranceFromString(String string) throws IllegalArgumentException {
+ Utterance utterance = new Utterance();
+ Markup markup = Markup.markupFromString(string);
+ if (!markup.getType().equals(TYPE_UTTERANCE)) {
+ throw new IllegalArgumentException("Top level markup should be of type \"" +
+ TYPE_UTTERANCE + "\", but was of type \"" +
+ markup.getType() + "\".") ;
+ }
+ for (Markup nestedMarkup : markup.getNestedMarkups()) {
+ utterance.append(nestedMarkup);
+ }
+ return utterance;
+ }
+
+ /**
+ * Appends a new TtsText with the given text.
+ * @param text The text to synthesize.
+ * @return This instance.
+ */
+ public Utterance append(String text) {
+ return append(new TtsText(text));
+ }
+
+ /**
+ * Appends a TtsCardinal representing the given number.
+ * @param integer The integer to synthesize.
+ * @return this
+ */
+ public Utterance append(int integer) {
+ return append(new TtsCardinal(integer));
+ }
+
+ /**
+ * Returns the n'th element in this Utterance.
+ * @param i The index.
+ * @return The n'th element in this Utterance.
+ * @throws {@link IndexOutOfBoundsException} - if i < 0 || i >= size()
+ */
+ public AbstractTts<? extends AbstractTts<?>> get(int i) {
+ return says.get(i);
+ }
+
+ /**
+ * Returns the number of elements in this Utterance.
+ * @return The number of elements in this Utterance.
+ */
+ public int size() {
+ return says.size();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if ( this == o ) return true;
+ if ( !(o instanceof Utterance) ) return false;
+ Utterance utt = (Utterance) o;
+
+ if (says.size() != utt.says.size()) {
+ return false;
+ }
+
+ for (int i = 0; i < says.size(); i++) {
+ if (!says.get(i).getMarkup().equals(utt.says.get(i).getMarkup())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Can be set to true or false, true indicating that the user provided only a string to the API,
+ * at which the system will not issue a warning if the synthesizer falls back onto the plain
+ * text when the synthesizer does not support Markup.
+ */
+ public Utterance setNoWarningOnFallback(boolean noWarning) {
+ mNoWarningOnFallback = noWarning;
+ return this;
+ }
+}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index c15ce44..5cd3d62 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -38,11 +38,11 @@ public class SurfaceControl {
private static native void nativeDestroy(long nativeObject);
private static native Bitmap nativeScreenshot(IBinder displayToken,
- int width, int height, int minLayer, int maxLayer, boolean allLayers,
- boolean useIdentityTransform);
+ Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
+ boolean allLayers, boolean useIdentityTransform);
private static native void nativeScreenshot(IBinder displayToken, Surface consumer,
- int width, int height, int minLayer, int maxLayer, boolean allLayers,
- boolean useIdentityTransform);
+ Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
+ boolean allLayers, boolean useIdentityTransform);
private static native void nativeOpenTransaction();
private static native void nativeCloseTransaction();
@@ -597,8 +597,8 @@ public class SurfaceControl {
public static void screenshot(IBinder display, Surface consumer,
int width, int height, int minLayer, int maxLayer,
boolean useIdentityTransform) {
- screenshot(display, consumer, width, height, minLayer, maxLayer, false,
- useIdentityTransform);
+ screenshot(display, consumer, new Rect(), width, height, minLayer, maxLayer,
+ false, useIdentityTransform);
}
/**
@@ -613,7 +613,7 @@ public class SurfaceControl {
*/
public static void screenshot(IBinder display, Surface consumer,
int width, int height) {
- screenshot(display, consumer, width, height, 0, 0, true, false);
+ screenshot(display, consumer, new Rect(), width, height, 0, 0, true, false);
}
/**
@@ -623,7 +623,7 @@ public class SurfaceControl {
* @param consumer The {@link Surface} to take the screenshot into.
*/
public static void screenshot(IBinder display, Surface consumer) {
- screenshot(display, consumer, 0, 0, 0, 0, true, false);
+ screenshot(display, consumer, new Rect(), 0, 0, 0, 0, true, false);
}
/**
@@ -634,6 +634,8 @@ public class SurfaceControl {
* the versions that use a {@link Surface} instead, such as
* {@link SurfaceControl#screenshot(IBinder, Surface)}.
*
+ * @param sourceCrop The portion of the screen to capture into the Bitmap;
+ * caller may pass in 'new Rect()' if no cropping is desired.
* @param width The desired width of the returned bitmap; the raw
* screen will be scaled down to this size.
* @param height The desired height of the returned bitmap; the raw
@@ -649,13 +651,13 @@ public class SurfaceControl {
* if an error occurs. Make sure to call Bitmap.recycle() as soon as
* possible, once its content is not needed anymore.
*/
- public static Bitmap screenshot(int width, int height, int minLayer, int maxLayer,
- boolean useIdentityTransform) {
+ public static Bitmap screenshot(Rect sourceCrop, int width, int height,
+ int minLayer, int maxLayer, boolean useIdentityTransform) {
// TODO: should take the display as a parameter
IBinder displayToken = SurfaceControl.getBuiltInDisplay(
SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
- return nativeScreenshot(displayToken, width, height, minLayer, maxLayer, false,
- useIdentityTransform);
+ return nativeScreenshot(displayToken, sourceCrop, width, height,
+ minLayer, maxLayer, false, useIdentityTransform);
}
/**
@@ -674,10 +676,10 @@ public class SurfaceControl {
// TODO: should take the display as a parameter
IBinder displayToken = SurfaceControl.getBuiltInDisplay(
SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
- return nativeScreenshot(displayToken, width, height, 0, 0, true, false);
+ return nativeScreenshot(displayToken, new Rect(), width, height, 0, 0, true, false);
}
- private static void screenshot(IBinder display, Surface consumer,
+ private static void screenshot(IBinder display, Surface consumer, Rect sourceCrop,
int width, int height, int minLayer, int maxLayer, boolean allLayers,
boolean useIdentityTransform) {
if (display == null) {
@@ -686,7 +688,7 @@ public class SurfaceControl {
if (consumer == null) {
throw new IllegalArgumentException("consumer must not be null");
}
- nativeScreenshot(display, consumer, width, height, minLayer, maxLayer, allLayers,
- useIdentityTransform);
+ nativeScreenshot(display, consumer, sourceCrop, width, height,
+ minLayer, maxLayer, allLayers, useIdentityTransform);
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 025cf69..ce266d7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -9236,6 +9236,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Request unbuffered dispatch of the given stream of MotionEvents to this View.
+ *
+ * Until this View receives a corresponding {@link MotionEvent#ACTION_UP}, ask that the input
+ * system not batch {@link MotionEvent}s but instead deliver them as soon as they're
+ * available. This method should only be called for touch events.
+ *
+ * <p class="note">This api is not intended for most applications. Buffered dispatch
+ * provides many of benefits, and just requesting unbuffered dispatch on most MotionEvent
+ * streams will not improve your input latency. Side effects include: increased latency,
+ * jittery scrolls and inability to take advantage of system resampling. Talk to your input
+ * professional to see if {@link #requestUnbufferedDispatch(MotionEvent)} is right for
+ * you.</p>
+ */
+ public final void requestUnbufferedDispatch(MotionEvent event) {
+ final int action = event.getAction();
+ if (mAttachInfo == null
+ || action != MotionEvent.ACTION_DOWN && action != MotionEvent.ACTION_MOVE
+ || !event.isTouchEvent()) {
+ return;
+ }
+ mAttachInfo.mUnbufferedDispatchRequested = true;
+ }
+
+ /**
* Set flags controlling behavior of this view.
*
* @param flags Constant indicating the value which should be set
@@ -19760,6 +19784,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
boolean mInTouchMode;
/**
+ * Indicates whether the view has requested unbuffered input dispatching for the current
+ * event stream.
+ */
+ boolean mUnbufferedDispatchRequested;
+
+ /**
* Indicates that ViewAncestor should trigger a global layout change
* the next time it performs a traversal
*/
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index aa06d15..ac25b57 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -230,6 +230,7 @@ public final class ViewRootImpl implements ViewParent,
QueuedInputEvent mPendingInputEventTail;
int mPendingInputEventCount;
boolean mProcessInputEventsScheduled;
+ boolean mUnbufferedInputDispatch;
String mPendingInputEventQueueLengthCounterName = "pq";
InputStage mFirstInputStage;
@@ -1016,7 +1017,9 @@ public final class ViewRootImpl implements ViewParent,
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
- scheduleConsumeBatchedInput();
+ if (!mUnbufferedInputDispatch) {
+ scheduleConsumeBatchedInput();
+ }
notifyRendererOfFramePending();
}
}
@@ -2616,7 +2619,7 @@ public final class ViewRootImpl implements ViewParent,
}
final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
- final Rect bounds = mView.mAttachInfo.mTmpInvalRect;
+ final Rect bounds = mAttachInfo.mTmpInvalRect;
if (provider == null) {
host.getBoundsOnScreen(bounds);
} else if (mAccessibilityFocusedVirtualView != null) {
@@ -3898,6 +3901,18 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ @Override
+ protected void onDeliverToNext(QueuedInputEvent q) {
+ if (mUnbufferedInputDispatch
+ && q.mEvent instanceof MotionEvent
+ && ((MotionEvent)q.mEvent).isTouchEvent()
+ && isTerminalInputEvent(q.mEvent)) {
+ mUnbufferedInputDispatch = false;
+ scheduleConsumeBatchedInput();
+ }
+ super.onDeliverToNext(q);
+ }
+
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
@@ -4010,10 +4025,15 @@ public final class ViewRootImpl implements ViewParent,
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
- if (mView.dispatchPointerEvent(event)) {
- return FINISH_HANDLED;
+ mAttachInfo.mUnbufferedDispatchRequested = false;
+ boolean handled = mView.dispatchPointerEvent(event);
+ if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
+ mUnbufferedInputDispatch = true;
+ if (mConsumeBatchedInputScheduled) {
+ scheduleConsumeBatchedInputImmediately();
+ }
}
- return FORWARD;
+ return handled ? FINISH_HANDLED : FORWARD;
}
private int processTrackballEvent(QueuedInputEvent q) {
@@ -5278,6 +5298,8 @@ public final class ViewRootImpl implements ViewParent,
writer.print(" mRemoved="); writer.println(mRemoved);
writer.print(innerPrefix); writer.print("mConsumeBatchedInputScheduled=");
writer.println(mConsumeBatchedInputScheduled);
+ writer.print(innerPrefix); writer.print("mConsumeBatchedInputImmediatelyScheduled=");
+ writer.println(mConsumeBatchedInputImmediatelyScheduled);
writer.print(innerPrefix); writer.print("mPendingInputEventCount=");
writer.println(mPendingInputEventCount);
writer.print(innerPrefix); writer.print("mProcessInputEventsScheduled=");
@@ -5688,6 +5710,7 @@ public final class ViewRootImpl implements ViewParent,
private void finishInputEvent(QueuedInputEvent q) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
q.mEvent.getSequenceNumber());
+
if (q.mReceiver != null) {
boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0;
q.mReceiver.finishInputEvent(q.mEvent, handled);
@@ -5727,15 +5750,25 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ void scheduleConsumeBatchedInputImmediately() {
+ if (!mConsumeBatchedInputImmediatelyScheduled) {
+ unscheduleConsumeBatchedInput();
+ mConsumeBatchedInputImmediatelyScheduled = true;
+ mHandler.post(mConsumeBatchedInputImmediatelyRunnable);
+ }
+ }
+
void doConsumeBatchedInput(long frameTimeNanos) {
if (mConsumeBatchedInputScheduled) {
mConsumeBatchedInputScheduled = false;
if (mInputEventReceiver != null) {
- if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)) {
+ if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)
+ && frameTimeNanos != -1) {
// If we consumed a batch here, we want to go ahead and schedule the
// consumption of batched input events on the next frame. Otherwise, we would
// wait until we have more input events pending and might get starved by other
- // things occurring in the process.
+ // things occurring in the process. If the frame time is -1, however, then
+ // we're in a non-batching mode, so there's no need to schedule this.
scheduleConsumeBatchedInput();
}
}
@@ -5763,7 +5796,11 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void onBatchedInputEventPending() {
- scheduleConsumeBatchedInput();
+ if (mUnbufferedInputDispatch) {
+ super.onBatchedInputEventPending();
+ } else {
+ scheduleConsumeBatchedInput();
+ }
}
@Override
@@ -5784,6 +5821,16 @@ public final class ViewRootImpl implements ViewParent,
new ConsumeBatchedInputRunnable();
boolean mConsumeBatchedInputScheduled;
+ final class ConsumeBatchedInputImmediatelyRunnable implements Runnable {
+ @Override
+ public void run() {
+ doConsumeBatchedInput(-1);
+ }
+ }
+ final ConsumeBatchedInputImmediatelyRunnable mConsumeBatchedInputImmediatelyRunnable =
+ new ConsumeBatchedInputImmediatelyRunnable();
+ boolean mConsumeBatchedInputImmediatelyScheduled;
+
final class InvalidateOnAnimationRunnable implements Runnable {
private boolean mPosted;
private final ArrayList<View> mViews = new ArrayList<View>();
diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java
index 628da3c..84f395a 100644
--- a/core/java/android/view/textservice/SpellCheckerSession.java
+++ b/core/java/android/view/textservice/SpellCheckerSession.java
@@ -427,8 +427,12 @@ public class SpellCheckerSession {
@Override
public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
- mHandler.sendMessage(
- Message.obtain(mHandler, MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results));
+ synchronized (this) {
+ if (mHandler != null) {
+ mHandler.sendMessage(Message.obtain(mHandler,
+ MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE, results));
+ }
+ }
}
}
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 265dbcd..2c1a77c 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -24,6 +24,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.text.InputType;
+import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.Log;
@@ -814,8 +815,7 @@ public class DatePicker extends FrameLayout {
mSpinners.removeAllViews();
// We use numeric spinners for year and day, but textual months. Ask icu4c what
// order the user's locale uses for that combination. http://b/7207103.
- String pattern = ICU.getBestDateTimePattern("yyyyMMMdd",
- Locale.getDefault().toString());
+ String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMdd");
char[] order = ICU.getDateFormatOrder(pattern);
final int spinnerCount = order.length;
for (int i = 0; i < spinnerCount; i++) {
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index 8511601..defc26c 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -104,14 +104,16 @@ import static java.lang.Math.min;
*
* <h4>Excess Space Distribution</h4>
*
- * GridLayout's distribution of excess space is based on <em>priority</em>
- * rather than <em>weight</em>.
+ * As of API 21, GridLayout's distribution of excess space accomodates the principle of weight.
+ * In the event that no weights are specified, the previous conventions are respected and
+ * columns and rows are taken as flexible if their views specify some form of alignment
+ * within their groups.
* <p>
- * A child's ability to stretch is inferred from the alignment properties of
- * its row and column groups (which are typically set by setting the
- * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters).
- * If alignment was defined along a given axis then the component
- * is taken as <em>flexible</em> in that direction. If no alignment was set,
+ * The flexibility of a view is therefore influenced by its alignment which is,
+ * in turn, typically defined by setting the
+ * {@link LayoutParams#setGravity(int) gravity} property of the child's layout parameters.
+ * If either a weight or alignment were defined along a given axis then the component
+ * is taken as <em>flexible</em> in that direction. If no weight or alignment was set,
* the component is instead assumed to be <em>inflexible</em>.
* <p>
* Multiple components in the same row or column group are
@@ -122,12 +124,16 @@ import static java.lang.Math.min;
* elements is flexible if <em>one</em> of its elements is flexible.
* <p>
* To make a column stretch, make sure all of the components inside it define a
- * gravity. To prevent a column from stretching, ensure that one of the components
- * in the column does not define a gravity.
+ * weight or a gravity. To prevent a column from stretching, ensure that one of the components
+ * in the column does not define a weight or a gravity.
* <p>
* When the principle of flexibility does not provide complete disambiguation,
* GridLayout's algorithms favour rows and columns that are closer to its <em>right</em>
- * and <em>bottom</em> edges.
+ * and <em>bottom</em> edges. To be more precise, GridLayout treats each of its layout
+ * parameters as a constraint in the a set of variables that define the grid-lines along a
+ * given axis. During layout, GridLayout solves the constraints so as to return the unique
+ * solution to those constraints for which all variables are less-than-or-equal-to
+ * the corresponding value in any other valid solution.
*
* <h4>Interpretation of GONE</h4>
*
@@ -140,18 +146,6 @@ import static java.lang.Math.min;
* had never been added to it.
* These statements apply equally to rows as well as columns, and to groups of rows or columns.
*
- * <h5>Limitations</h5>
- *
- * GridLayout does not provide support for the principle of <em>weight</em>, as defined in
- * {@link LinearLayout.LayoutParams#weight}. In general, it is not therefore possible
- * to configure a GridLayout to distribute excess space between multiple components.
- * <p>
- * Some common use-cases may nevertheless be accommodated as follows.
- * To place equal amounts of space around a component in a cell group;
- * use {@link #CENTER} alignment (or {@link LayoutParams#setGravity(int) gravity}).
- * For complete control over excess space distribution in a row or column;
- * use a {@link LinearLayout} subview to hold the components in the associated cell group.
- * When using either of these techniques, bear in mind that cell groups may be defined to overlap.
* <p>
* See {@link GridLayout.LayoutParams} for a full description of the
* layout parameters used by GridLayout.
@@ -1018,6 +1012,8 @@ public class GridLayout extends ViewGroup {
LayoutParams lp = getLayoutParams(c);
if (firstPass) {
measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height);
+ mHorizontalAxis.recordOriginalMeasurement(i);
+ mVerticalAxis.recordOriginalMeasurement(i);
} else {
boolean horizontal = (mOrientation == HORIZONTAL);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
@@ -1245,6 +1241,11 @@ public class GridLayout extends ViewGroup {
public int[] locations;
public boolean locationsValid = false;
+ public boolean hasWeights;
+ public boolean hasWeightsValid = false;
+ public int[] originalMeasurements;
+ public int[] deltas;
+
boolean orderPreserved = DEFAULT_ORDER_PRESERVED;
private MutableInt parentMin = new MutableInt(0);
@@ -1321,7 +1322,10 @@ public class GridLayout extends ViewGroup {
// we must include views that are GONE here, see introductory javadoc
LayoutParams lp = getLayoutParams(c);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
- groupBounds.getValue(i).include(GridLayout.this, c, spec, this);
+ int size = (spec.weight == 0) ?
+ getMeasurementIncludingMargin(c, horizontal) :
+ getOriginalMeasurements()[i] + getDeltas()[i];
+ groupBounds.getValue(i).include(GridLayout.this, c, spec, this, size);
}
}
@@ -1693,8 +1697,94 @@ public class GridLayout extends ViewGroup {
return trailingMargins;
}
- private void computeLocations(int[] a) {
+ private void solve(int[] a) {
solve(getArcs(), a);
+ }
+
+ private boolean computeHasWeights() {
+ for (int i = 0, N = getChildCount(); i < N; i++) {
+ LayoutParams lp = getLayoutParams(getChildAt(i));
+ Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+ if (spec.weight != 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean hasWeights() {
+ if (!hasWeightsValid) {
+ hasWeights = computeHasWeights();
+ hasWeightsValid = true;
+ }
+ return hasWeights;
+ }
+
+ public int[] getOriginalMeasurements() {
+ if (originalMeasurements == null) {
+ originalMeasurements = new int[getChildCount()];
+ }
+ return originalMeasurements;
+ }
+
+ private void recordOriginalMeasurement(int i) {
+ if (hasWeights()) {
+ getOriginalMeasurements()[i] = getMeasurementIncludingMargin(getChildAt(i), horizontal);
+ }
+ }
+
+ public int[] getDeltas() {
+ if (deltas == null) {
+ deltas = new int[getChildCount()];
+ }
+ return deltas;
+ }
+
+ private void shareOutDelta() {
+ int totalDelta = 0;
+ float totalWeight = 0;
+ for (int i = 0, N = getChildCount(); i < N; i++) {
+ View c = getChildAt(i);
+ LayoutParams lp = getLayoutParams(c);
+ Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+ float weight = spec.weight;
+ if (weight != 0) {
+ int delta = getMeasurement(c, horizontal) - getOriginalMeasurements()[i];
+ totalDelta += delta;
+ totalWeight += weight;
+ }
+ }
+ for (int i = 0, N = getChildCount(); i < N; i++) {
+ LayoutParams lp = getLayoutParams(getChildAt(i));
+ Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
+ float weight = spec.weight;
+ if (weight != 0) {
+ int delta = Math.round((weight * totalDelta / totalWeight));
+ deltas[i] = delta;
+ // the two adjustments below are to counter the above rounding and avoid off-by-ones at the end
+ totalDelta -= delta;
+ totalWeight -= weight;
+ }
+ }
+ }
+
+ private void solveAndDistributeSpace(int[] a) {
+ Arrays.fill(getDeltas(), 0);
+ solve(a);
+ shareOutDelta();
+ arcsValid = false;
+ forwardLinksValid = false;
+ backwardLinksValid = false;
+ groupBoundsValid = false;
+ solve(a);
+ }
+
+ private void computeLocations(int[] a) {
+ if (!hasWeights()) {
+ solve(a);
+ } else {
+ solveAndDistributeSpace(a);
+ }
if (!orderPreserved) {
// Solve returns the smallest solution to the constraint system for which all
// values are positive. One value is therefore zero - though if the row/col
@@ -1777,6 +1867,10 @@ public class GridLayout extends ViewGroup {
locations = null;
+ originalMeasurements = null;
+ deltas = null;
+ hasWeightsValid = false;
+
invalidateValues();
}
@@ -1810,6 +1904,9 @@ public class GridLayout extends ViewGroup {
* both aspects of alignment within the cell group. It is also possible to specify a child's
* alignment within its cell group by using the {@link GridLayout.LayoutParams#setGravity(int)}
* method.
+ * <p>
+ * The weight property is also included in Spec and specifies the proportion of any
+ * excess space that is due to the associated view.
*
* <h4>WRAP_CONTENT and MATCH_PARENT</h4>
*
@@ -1851,9 +1948,11 @@ public class GridLayout extends ViewGroup {
* <li>{@link #rowSpec}<code>.row</code> = {@link #UNDEFINED} </li>
* <li>{@link #rowSpec}<code>.rowSpan</code> = 1 </li>
* <li>{@link #rowSpec}<code>.alignment</code> = {@link #BASELINE} </li>
+ * <li>{@link #rowSpec}<code>.weight</code> = 0 </li>
* <li>{@link #columnSpec}<code>.column</code> = {@link #UNDEFINED} </li>
* <li>{@link #columnSpec}<code>.columnSpan</code> = 1 </li>
* <li>{@link #columnSpec}<code>.alignment</code> = {@link #START} </li>
+ * <li>{@link #columnSpec}<code>.weight</code> = 0 </li>
* </ul>
*
* See {@link GridLayout} for a more complete description of the conventions
@@ -1861,8 +1960,10 @@ public class GridLayout extends ViewGroup {
*
* @attr ref android.R.styleable#GridLayout_Layout_layout_row
* @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan
+ * @attr ref android.R.styleable#GridLayout_Layout_layout_rowWeight
* @attr ref android.R.styleable#GridLayout_Layout_layout_column
* @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan
+ * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight
* @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
*/
public static class LayoutParams extends MarginLayoutParams {
@@ -1889,9 +1990,11 @@ public class GridLayout extends ViewGroup {
private static final int COLUMN = R.styleable.GridLayout_Layout_layout_column;
private static final int COLUMN_SPAN = R.styleable.GridLayout_Layout_layout_columnSpan;
+ private static final int COLUMN_WEIGHT = R.styleable.GridLayout_Layout_layout_columnWeight;
private static final int ROW = R.styleable.GridLayout_Layout_layout_row;
private static final int ROW_SPAN = R.styleable.GridLayout_Layout_layout_rowSpan;
+ private static final int ROW_WEIGHT = R.styleable.GridLayout_Layout_layout_rowWeight;
private static final int GRAVITY = R.styleable.GridLayout_Layout_layout_gravity;
@@ -2034,11 +2137,13 @@ public class GridLayout extends ViewGroup {
int column = a.getInt(COLUMN, DEFAULT_COLUMN);
int colSpan = a.getInt(COLUMN_SPAN, DEFAULT_SPAN_SIZE);
- this.columnSpec = spec(column, colSpan, getAlignment(gravity, true));
+ float colWeight = a.getFloat(COLUMN_WEIGHT, Spec.DEFAULT_WEIGHT);
+ this.columnSpec = spec(column, colSpan, getAlignment(gravity, true), colWeight);
int row = a.getInt(ROW, DEFAULT_ROW);
int rowSpan = a.getInt(ROW_SPAN, DEFAULT_SPAN_SIZE);
- this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false));
+ float rowWeight = a.getFloat(ROW_WEIGHT, Spec.DEFAULT_WEIGHT);
+ this.rowSpec = spec(row, rowSpan, getAlignment(gravity, false), rowWeight);
} finally {
a.recycle();
}
@@ -2273,10 +2378,9 @@ public class GridLayout extends ViewGroup {
return before - a.getAlignmentValue(c, size, gl.getLayoutMode());
}
- protected final void include(GridLayout gl, View c, Spec spec, Axis axis) {
+ protected final void include(GridLayout gl, View c, Spec spec, Axis axis, int size) {
this.flexibility &= spec.getFlexibility();
boolean horizontal = axis.horizontal;
- int size = gl.getMeasurementIncludingMargin(c, horizontal);
Alignment alignment = gl.getAlignment(spec.alignment, horizontal);
// todo test this works correctly when the returned value is UNDEFINED
int before = alignment.getAlignmentValue(c, size, gl.getLayoutMode());
@@ -2401,36 +2505,43 @@ public class GridLayout extends ViewGroup {
* <li>{@link #spec(int, int)}</li>
* <li>{@link #spec(int, Alignment)}</li>
* <li>{@link #spec(int, int, Alignment)}</li>
+ * <li>{@link #spec(int, float)}</li>
+ * <li>{@link #spec(int, int, float)}</li>
+ * <li>{@link #spec(int, Alignment, float)}</li>
+ * <li>{@link #spec(int, int, Alignment, float)}</li>
* </ul>
*
*/
public static class Spec {
static final Spec UNDEFINED = spec(GridLayout.UNDEFINED);
+ static final float DEFAULT_WEIGHT = 0;
final boolean startDefined;
final Interval span;
final Alignment alignment;
+ final float weight;
- private Spec(boolean startDefined, Interval span, Alignment alignment) {
+ private Spec(boolean startDefined, Interval span, Alignment alignment, float weight) {
this.startDefined = startDefined;
this.span = span;
this.alignment = alignment;
+ this.weight = weight;
}
- private Spec(boolean startDefined, int start, int size, Alignment alignment) {
- this(startDefined, new Interval(start, start + size), alignment);
+ private Spec(boolean startDefined, int start, int size, Alignment alignment, float weight) {
+ this(startDefined, new Interval(start, start + size), alignment, weight);
}
final Spec copyWriteSpan(Interval span) {
- return new Spec(startDefined, span, alignment);
+ return new Spec(startDefined, span, alignment, weight);
}
final Spec copyWriteAlignment(Alignment alignment) {
- return new Spec(startDefined, span, alignment);
+ return new Spec(startDefined, span, alignment, weight);
}
final int getFlexibility() {
- return (alignment == UNDEFINED_ALIGNMENT) ? INFLEXIBLE : CAN_STRETCH;
+ return (alignment == UNDEFINED_ALIGNMENT && weight == 0) ? INFLEXIBLE : CAN_STRETCH;
}
/**
@@ -2478,6 +2589,7 @@ public class GridLayout extends ViewGroup {
* <ul>
* <li> {@code spec.span = [start, start + size]} </li>
* <li> {@code spec.alignment = alignment} </li>
+ * <li> {@code spec.weight = weight} </li>
* </ul>
* <p>
* To leave the start index undefined, use the value {@link #UNDEFINED}.
@@ -2485,9 +2597,55 @@ public class GridLayout extends ViewGroup {
* @param start the start
* @param size the size
* @param alignment the alignment
+ * @param weight the weight
+ */
+ public static Spec spec(int start, int size, Alignment alignment, float weight) {
+ return new Spec(start != UNDEFINED, start, size, alignment, weight);
+ }
+
+ /**
+ * Equivalent to: {@code spec(start, 1, alignment, weight)}.
+ *
+ * @param start the start
+ * @param alignment the alignment
+ * @param weight the weight
+ */
+ public static Spec spec(int start, Alignment alignment, float weight) {
+ return spec(start, 1, alignment, weight);
+ }
+
+ /**
+ * Equivalent to: {@code spec(start, 1, default_alignment, weight)} -
+ * where {@code default_alignment} is specified in
+ * {@link android.widget.GridLayout.LayoutParams}.
+ *
+ * @param start the start
+ * @param size the size
+ * @param weight the weight
+ */
+ public static Spec spec(int start, int size, float weight) {
+ return spec(start, size, UNDEFINED_ALIGNMENT, weight);
+ }
+
+ /**
+ * Equivalent to: {@code spec(start, 1, weight)}.
+ *
+ * @param start the start
+ * @param weight the weight
+ */
+ public static Spec spec(int start, float weight) {
+ return spec(start, 1, weight);
+ }
+
+ /**
+ * Equivalent to: {@code spec(start, size, alignment, 0f)}.
+ *
+ * @param start the start
+ * @param size the size
+ * @param alignment the alignment
*/
public static Spec spec(int start, int size, Alignment alignment) {
- return new Spec(start != UNDEFINED, start, size, alignment);
+ return spec(start, size, alignment, Spec.DEFAULT_WEIGHT);
}
/**