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.java70
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl7
-rw-r--r--core/java/android/app/backup/BackupTransport.java415
-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.aidl15
-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/hardware/hdmi/HdmiCec.java4
-rw-r--r--core/java/android/net/ConnectivityManager.java223
-rw-r--r--core/java/android/net/ConnectivityServiceProtocol.java70
-rw-r--r--core/java/android/net/IConnectivityManager.aidl4
-rw-r--r--core/java/android/net/NetworkAgent.java337
-rw-r--r--core/java/android/net/NetworkFactory.java275
-rw-r--r--core/java/android/net/NetworkInfo.java14
-rw-r--r--core/java/android/net/NetworkRequest.java22
-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/GLES20Canvas.java114
-rw-r--r--core/java/android/view/GLES20RecordingCanvas.java2
-rw-r--r--core/java/android/view/GLRenderer.java1521
-rw-r--r--core/java/android/view/HardwareCanvas.java42
-rw-r--r--core/java/android/view/HardwareLayer.java37
-rw-r--r--core/java/android/view/HardwareRenderer.java15
-rw-r--r--core/java/android/view/SurfaceControl.java34
-rw-r--r--core/java/android/view/ThreadedRenderer.java10
-rw-r--r--core/java/android/view/View.java36
-rw-r--r--core/java/android/view/ViewRootImpl.java74
-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
54 files changed, 3866 insertions, 2357 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 ac25a53..e03224c 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..afe2981 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;
@@ -103,6 +104,17 @@ public class DevicePolicyManager {
= "android.app.action.ACTION_PROVISION_MANAGED_PROFILE";
/**
+ * A broadcast intent with this action can be sent to ManagedProvisionning to specify that the
+ * user has already consented to the creation of the managed profile.
+ * The intent must contain the extras
+ * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME} and
+ * {@link #EXTRA_PROVISIONING_TOKEN}
+ * @hide
+ */
+ public static final String ACTION_PROVISIONING_USER_HAS_CONSENTED
+ = "android.app.action.USER_HAS_CONSENTED";
+
+ /**
* A String extra holding the name of the package of the mobile device management application
* that starts the managed provisioning flow. This package will be set as the profile owner.
* <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}.
@@ -111,6 +123,13 @@ public class DevicePolicyManager {
= "deviceAdminPackageName";
/**
+ * An int extra used to identify the consent of the user to create the managed profile.
+ * <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}
+ */
+ public static final String EXTRA_PROVISIONING_TOKEN
+ = "android.app.extra.token";
+
+ /**
* A String extra holding the default name of the profile that is created during managed profile
* provisioning.
* <p>Use with {@link #ACTION_PROVISION_MANAGED_PROFILE}
@@ -176,15 +195,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 +1971,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 +1988,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 +2339,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..46f082e
--- /dev/null
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -0,0 +1,415 @@
+/*
+ * 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.IBackupTransport;
+
+/**
+ * Concrete class that provides a stable-API bridge between IBackupTransport
+ * and its implementations.
+ *
+ * @hide
+ */
+public class BackupTransport {
+ public static final int TRANSPORT_OK = 0;
+ public static final int TRANSPORT_ERROR = 1;
+ public static final int TRANSPORT_NOT_INITIALIZED = 2;
+ public static final int TRANSPORT_PACKAGE_REJECTED = 3;
+ public static final int AGENT_ERROR = 4;
+ public static final int AGENT_UNKNOWN = 5;
+
+ 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");
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Device-level operations common to both key/value and full-data storage
+
+ /**
+ * 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 BackupTransport#TRANSPORT_OK} (OK so far) or
+ * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure).
+ */
+ public int initializeDevice() {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ /**
+ * Erase the given 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 BackupTransport.TRANSPORT_ERROR;
+ }
+
+ /**
+ * Finish sending application data to the backup destination. This must be
+ * called after {@link #performBackup}, {@link #performFullBackup}, or {@link clearBackupData}
+ * to ensure that all data is sent and the operation properly finalized. Only when this
+ * method returns true can a backup be assumed to have succeeded.
+ *
+ * @return the same error codes as {@link #performBackup} or {@link #performFullBackup}.
+ */
+ public int finishBackup() {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Key/value incremental backup support interfaces
+
+ /**
+ * Verify that this is a suitable time for a key/value 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;
+ }
+
+ /**
+ * 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 BackupTransport#TRANSPORT_OK} (OK so far),
+ * {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure), or
+ * {@link BackupTransport#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 BackupTransport.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 BackupTransport#TRANSPORT_OK} (OK so far, call
+ * {@link #nextRestorePackage}) or {@link BackupTransport#TRANSPORT_ERROR}
+ * (an error occurred, the restore should be aborted and rescheduled).
+ */
+ public int startRestore(long token, PackageInfo[] packages) {
+ return BackupTransport.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 BackupTransport.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");
+ }
+
+ // ------------------------------------------------------------------------------------
+ // Full backup interfaces
+
+ /**
+ * Verify that this is a suitable time for a full-data 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 #performFullBackup}/{@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.
+ *
+ * @see #requestBackupTime()
+ */
+ public long requestFullBackupTime() {
+ return 0;
+ }
+
+ /**
+ * Begin the process of sending an application's full-data archive to the backend.
+ * The description of the package whose data will be delivered is provided, as well as
+ * the socket file descriptor on which the transport will receive the data itself.
+ *
+ * <p>If the package is not eligible for backup, the transport should return
+ * {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED}. In this case the system will
+ * simply proceed with the next candidate if any, or finish the full backup operation
+ * if all apps have been processed.
+ *
+ * <p>After the transport returns {@link BackupTransport#TRANSPORT_OK} from this
+ * method, the OS will proceed to call {@link #sendBackupData()} one or more times
+ * to deliver the application's data as a streamed tarball. The transport should not
+ * read() from the socket except as instructed to via the {@link #sendBackupData(int)}
+ * method.
+ *
+ * <p>After all data has been delivered to the transport, the system will call
+ * {@link #finishBackup()}. At this point the transport should commit the data to
+ * its datastore, if appropriate, and close the socket that had been provided in
+ * {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)}.
+ *
+ * @param targetPackage The package whose data is to follow.
+ * @param socket The socket file descriptor through which the data will be provided.
+ * If the transport returns {@link #TRANSPORT_PACKAGE_REJECTED} here, it must still
+ * close this file descriptor now; otherwise it should be cached for use during
+ * succeeding calls to {@link #sendBackupData(int)}, and closed in response to
+ * {@link #finishBackup()}.
+ * @return TRANSPORT_PACKAGE_REJECTED to indicate that the stated application is not
+ * to be backed up; TRANSPORT_OK to indicate that the OS may proceed with delivering
+ * backup data; TRANSPORT_ERROR to indicate a fatal error condition that precludes
+ * performing a backup at this time.
+ */
+ public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) {
+ return BackupTransport.TRANSPORT_PACKAGE_REJECTED;
+ }
+
+ /**
+ * Tells the transport to read {@code numBytes} bytes of data from the socket file
+ * descriptor provided in the {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)}
+ * call, and deliver those bytes to the datastore.
+ *
+ * @param numBytes The number of bytes of tarball data available to be read from the
+ * socket.
+ * @return TRANSPORT_OK on successful processing of the data; TRANSPORT_ERROR to
+ * indicate a fatal error situation. If an error is returned, the system will
+ * call finishBackup() and stop attempting backups until after a backoff and retry
+ * interval.
+ */
+ public int sendBackupData(int numBytes) {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ /**
+ * 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 e8885bf..a040efb 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..70668e1 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
@@ -433,6 +433,13 @@ interface IPackageManager {
in VerificationParams verificationParams,
in ContainerEncryptionParams encryptionParams);
+ void installPackageWithVerificationEncryptionAndAbiOverrideEtc(in Uri packageURI,
+ in IPackageInstallObserver observer, in IPackageInstallObserver2 observer2,
+ int flags, in String installerPackageName,
+ in VerificationParams verificationParams,
+ in ContainerEncryptionParams encryptionParams,
+ in String packageAbiOverride);
+
int installExistingPackageAsUser(String packageName, int userId);
void verifyPendingInstall(int id, int verificationCode);
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/hardware/hdmi/HdmiCec.java b/core/java/android/hardware/hdmi/HdmiCec.java
index a71a74d..723eda1 100644
--- a/core/java/android/hardware/hdmi/HdmiCec.java
+++ b/core/java/android/hardware/hdmi/HdmiCec.java
@@ -194,6 +194,8 @@ public final class HdmiCec {
DEVICE_RECORDER, // ADDR_RECORDER_3
DEVICE_TUNER, // ADDR_TUNER_4
DEVICE_PLAYBACK, // ADDR_PLAYBACK_3
+ DEVICE_RESERVED,
+ DEVICE_RESERVED,
DEVICE_TV, // ADDR_SPECIFIC_USE
};
@@ -210,6 +212,8 @@ public final class HdmiCec {
"Recorder_3",
"Tuner_4",
"Playback_3",
+ "Reserved_1",
+ "Reserved_2",
"Secondary_TV",
};
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 2f2aba3..a48a388 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -40,6 +40,7 @@ import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.PhoneConstants;
import com.android.internal.util.Protocol;
import java.net.InetAddress;
@@ -807,11 +808,34 @@ public class ConnectivityManager {
* @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api.
*/
public int startUsingNetworkFeature(int networkType, String feature) {
- try {
- return mService.startUsingNetworkFeature(networkType, feature,
- new Binder());
- } catch (RemoteException e) {
- return -1;
+ NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
+ if (netCap == null) {
+ Log.d(TAG, "Can't satisfy startUsingNetworkFeature for " + networkType + ", " +
+ feature);
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+
+ NetworkRequest request = null;
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.get(netCap);
+ if (l != null) {
+ Log.d(TAG, "renewing startUsingNetworkFeature request " + l.networkRequest);
+ renewRequestLocked(l);
+ if (l.currentNetwork != null) {
+ return PhoneConstants.APN_ALREADY_ACTIVE;
+ } else {
+ return PhoneConstants.APN_REQUEST_STARTED;
+ }
+ }
+
+ request = requestNetworkForFeatureLocked(netCap);
+ }
+ if (request != null) {
+ Log.d(TAG, "starting startUsingNeworkFeature for request " + request);
+ return PhoneConstants.APN_REQUEST_STARTED;
+ } else {
+ Log.d(TAG, " request Failed");
+ return PhoneConstants.APN_REQUEST_FAILED;
}
}
@@ -831,11 +855,172 @@ public class ConnectivityManager {
* @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api.
*/
public int stopUsingNetworkFeature(int networkType, String feature) {
- try {
- return mService.stopUsingNetworkFeature(networkType, feature);
- } catch (RemoteException e) {
+ NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
+ if (netCap == null) {
+ Log.d(TAG, "Can't satisfy stopUsingNetworkFeature for " + networkType + ", " +
+ feature);
return -1;
}
+
+ NetworkRequest request = removeRequestForFeature(netCap);
+ if (request != null) {
+ Log.d(TAG, "stopUsingNetworkFeature for " + networkType + ", " + feature);
+ releaseNetworkRequest(request);
+ }
+ return 1;
+ }
+
+ private NetworkCapabilities networkCapabilitiesForFeature(int networkType, String feature) {
+ if (networkType == TYPE_MOBILE) {
+ int cap = -1;
+ if ("enableMMS".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_MMS;
+ } else if ("enableSUPL".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_SUPL;
+ } else if ("enableDUN".equals(feature) || "enableDUNAlways".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_DUN;
+ } else if ("enableHIPRI".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_INTERNET;
+ } else if ("enableFOTA".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_FOTA;
+ } else if ("enableIMS".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_IMS;
+ } else if ("enableCBS".equals(feature)) {
+ cap = NetworkCapabilities.NET_CAPABILITY_CBS;
+ } else {
+ return null;
+ }
+ NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+ netCap.addNetworkCapability(cap);
+ return netCap;
+ } else if (networkType == TYPE_WIFI) {
+ if ("p2p".equals(feature)) {
+ NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+ netCap.addNetworkCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P);
+ return netCap;
+ }
+ }
+ return null;
+ }
+
+ private int legacyTypeForNetworkCapabilities(NetworkCapabilities netCap) {
+ if (netCap == null) return TYPE_NONE;
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)) {
+ return TYPE_MOBILE_CBS;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_IMS)) {
+ return TYPE_MOBILE_IMS;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_FOTA)) {
+ return TYPE_MOBILE_FOTA;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN)) {
+ return TYPE_MOBILE_DUN;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
+ return TYPE_MOBILE_SUPL;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
+ return TYPE_MOBILE_MMS;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
+ return TYPE_MOBILE_HIPRI;
+ }
+ if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_WIFI_P2P)) {
+ return TYPE_WIFI_P2P;
+ }
+ return TYPE_NONE;
+ }
+
+ private static class LegacyRequest {
+ NetworkCapabilities networkCapabilities;
+ NetworkRequest networkRequest;
+ int expireSequenceNumber;
+ Network currentNetwork;
+ int delay = -1;
+ NetworkCallbackListener networkCallbackListener = new NetworkCallbackListener() {
+ @Override
+ public void onAvailable(NetworkRequest request, Network network) {
+ currentNetwork = network;
+ Log.d(TAG, "startUsingNetworkFeature got Network:" + network);
+ network.bindProcessForHostResolution();
+ }
+ @Override
+ public void onLost(NetworkRequest request, Network network) {
+ if (network.equals(currentNetwork)) {
+ currentNetwork = null;
+ network.unbindProcessForHostResolution();
+ }
+ Log.d(TAG, "startUsingNetworkFeature lost Network:" + network);
+ }
+ };
+ }
+
+ private HashMap<NetworkCapabilities, LegacyRequest> sLegacyRequests =
+ new HashMap<NetworkCapabilities, LegacyRequest>();
+
+ private NetworkRequest findRequestForFeature(NetworkCapabilities netCap) {
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.get(netCap);
+ if (l != null) return l.networkRequest;
+ }
+ return null;
+ }
+
+ private void renewRequestLocked(LegacyRequest l) {
+ l.expireSequenceNumber++;
+ Log.d(TAG, "renewing request to seqNum " + l.expireSequenceNumber);
+ sendExpireMsgForFeature(l.networkCapabilities, l.expireSequenceNumber, l.delay);
+ }
+
+ private void expireRequest(NetworkCapabilities netCap, int sequenceNum) {
+ int ourSeqNum = -1;
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.get(netCap);
+ if (l == null) return;
+ ourSeqNum = l.expireSequenceNumber;
+ if (l.expireSequenceNumber == sequenceNum) {
+ releaseNetworkRequest(l.networkRequest);
+ sLegacyRequests.remove(netCap);
+ }
+ }
+ Log.d(TAG, "expireRequest with " + ourSeqNum + ", " + sequenceNum);
+ }
+
+ private NetworkRequest requestNetworkForFeatureLocked(NetworkCapabilities netCap) {
+ int delay = -1;
+ int type = legacyTypeForNetworkCapabilities(netCap);
+ try {
+ delay = mService.getRestoreDefaultNetworkDelay(type);
+ } catch (RemoteException e) {}
+ LegacyRequest l = new LegacyRequest();
+ l.networkCapabilities = netCap;
+ l.delay = delay;
+ l.expireSequenceNumber = 0;
+ l.networkRequest = sendRequestForNetwork(netCap, l.networkCallbackListener, 0,
+ REQUEST, type);
+ if (l.networkRequest == null) return null;
+ sLegacyRequests.put(netCap, l);
+ sendExpireMsgForFeature(netCap, l.expireSequenceNumber, delay);
+ return l.networkRequest;
+ }
+
+ private void sendExpireMsgForFeature(NetworkCapabilities netCap, int seqNum, int delay) {
+ if (delay >= 0) {
+ Log.d(TAG, "sending expire msg with seqNum " + seqNum + " and delay " + delay);
+ Message msg = sCallbackHandler.obtainMessage(EXPIRE_LEGACY_REQUEST, seqNum, 0, netCap);
+ sCallbackHandler.sendMessageDelayed(msg, delay);
+ }
+ }
+
+ private NetworkRequest removeRequestForFeature(NetworkCapabilities netCap) {
+ synchronized (sLegacyRequests) {
+ LegacyRequest l = sLegacyRequests.remove(netCap);
+ if (l == null) return null;
+ return l.networkRequest;
+ }
}
/**
@@ -1782,8 +1967,10 @@ public class ConnectivityManager {
public static final int CALLBACK_RELEASED = BASE + 8;
/** @hide */
public static final int CALLBACK_EXIT = BASE + 9;
+ /** @hide obj = NetworkCapabilities, arg1 = seq number */
+ private static final int EXPIRE_LEGACY_REQUEST = BASE + 10;
- private static class CallbackHandler extends Handler {
+ private class CallbackHandler extends Handler {
private final HashMap<NetworkRequest, NetworkCallbackListener>mCallbackMap;
private final AtomicInteger mRefCount;
private static final String TAG = "ConnectivityManager.CallbackHandler";
@@ -1903,6 +2090,10 @@ public class ConnectivityManager {
getLooper().quit();
break;
}
+ case EXPIRE_LEGACY_REQUEST: {
+ expireRequest((NetworkCapabilities)message.obj, message.arg1);
+ break;
+ }
}
}
@@ -1954,8 +2145,9 @@ public class ConnectivityManager {
private final static int LISTEN = 1;
private final static int REQUEST = 2;
- private NetworkRequest somethingForNetwork(NetworkCapabilities need,
- NetworkCallbackListener networkCallbackListener, int timeoutSec, int action) {
+ private NetworkRequest sendRequestForNetwork(NetworkCapabilities need,
+ NetworkCallbackListener networkCallbackListener, int timeoutSec, int action,
+ int legacyType) {
NetworkRequest networkRequest = null;
if (networkCallbackListener == null) {
throw new IllegalArgumentException("null NetworkCallbackListener");
@@ -1968,7 +2160,7 @@ public class ConnectivityManager {
new Binder());
} else {
networkRequest = mService.requestNetwork(need, new Messenger(sCallbackHandler),
- timeoutSec, new Binder());
+ timeoutSec, new Binder(), legacyType);
}
if (networkRequest != null) {
synchronized(sNetworkCallbackListener) {
@@ -1998,7 +2190,7 @@ public class ConnectivityManager {
*/
public NetworkRequest requestNetwork(NetworkCapabilities need,
NetworkCallbackListener networkCallbackListener) {
- return somethingForNetwork(need, networkCallbackListener, 0, REQUEST);
+ return sendRequestForNetwork(need, networkCallbackListener, 0, REQUEST, TYPE_NONE);
}
/**
@@ -2021,7 +2213,8 @@ public class ConnectivityManager {
*/
public NetworkRequest requestNetwork(NetworkCapabilities need,
NetworkCallbackListener networkCallbackListener, int timeoutSec) {
- return somethingForNetwork(need, networkCallbackListener, timeoutSec, REQUEST);
+ return sendRequestForNetwork(need, networkCallbackListener, timeoutSec, REQUEST,
+ TYPE_NONE);
}
/**
@@ -2099,7 +2292,7 @@ public class ConnectivityManager {
*/
public NetworkRequest listenForNetwork(NetworkCapabilities need,
NetworkCallbackListener networkCallbackListener) {
- return somethingForNetwork(need, networkCallbackListener, 0, LISTEN);
+ return sendRequestForNetwork(need, networkCallbackListener, 0, LISTEN, TYPE_NONE);
}
/**
diff --git a/core/java/android/net/ConnectivityServiceProtocol.java b/core/java/android/net/ConnectivityServiceProtocol.java
deleted file mode 100644
index 74096b4..0000000
--- a/core/java/android/net/ConnectivityServiceProtocol.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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.net;
-
-import static com.android.internal.util.Protocol.BASE_CONNECTIVITY_SERVICE;
-
-/**
- * Describes the Internal protocols used to communicate with ConnectivityService.
- * @hide
- */
-public class ConnectivityServiceProtocol {
-
- private static final int BASE = BASE_CONNECTIVITY_SERVICE;
-
- private ConnectivityServiceProtocol() {}
-
- /**
- * This is a contract between ConnectivityService and various bearers.
- * A NetworkFactory is an abstract entity that creates NetworkAgent objects.
- * The bearers register with ConnectivityService using
- * ConnectivityManager.registerNetworkFactory, where they pass in a Messenger
- * to be used to deliver the following Messages.
- */
- public static class NetworkFactoryProtocol {
- private NetworkFactoryProtocol() {}
- /**
- * Pass a network request to the bearer. If the bearer believes it can
- * satisfy the request it should connect to the network and create a
- * NetworkAgent. Once the NetworkAgent is fully functional it will
- * register itself with ConnectivityService using registerNetworkAgent.
- * If the bearer cannot immediately satisfy the request (no network,
- * user disabled the radio, lower-scored network) it should remember
- * any NetworkRequests it may be able to satisfy in the future. It may
- * disregard any that it will never be able to service, for example
- * those requiring a different bearer.
- * msg.obj = NetworkRequest
- * msg.arg1 = score - the score of the any network currently satisfying this
- * request. If this bearer knows in advance it cannot
- * exceed this score it should not try to connect, holding the request
- * for the future.
- * Note that subsequent events may give a different (lower
- * or higher) score for this request, transmitted to each
- * NetworkFactory through additional CMD_REQUEST_NETWORK msgs
- * with the same NetworkRequest but an updated score.
- * Also, network conditions may change for this bearer
- * allowing for a better score in the future.
- */
- public static final int CMD_REQUEST_NETWORK = BASE;
-
- /**
- * Cancel a network request
- * msg.obj = NetworkRequest
- */
- public static final int CMD_CANCEL_REQUEST = BASE + 1;
- }
-}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index baec36a..5f1ff3e 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -158,7 +158,7 @@ interface IConnectivityManager
in NetworkCapabilities nc, int score);
NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities,
- in Messenger messenger, int timeoutSec, in IBinder binder);
+ in Messenger messenger, int timeoutSec, in IBinder binder, int legacy);
NetworkRequest pendingRequestForNetwork(in NetworkCapabilities networkCapabilities,
in PendingIntent operation);
@@ -170,4 +170,6 @@ interface IConnectivityManager
in PendingIntent operation);
void releaseNetworkRequest(in NetworkRequest networkRequest);
+
+ int getRestoreDefaultNetworkDelay(int networkType);
}
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 1c18ba5..7e8b1f1 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -24,85 +24,39 @@ import android.os.Messenger;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
-import android.util.SparseArray;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
+import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * A Utility class for handling NetworkRequests.
- *
- * Created by bearer-specific code to handle tracking requests, scores,
- * network data and handle communicating with ConnectivityService. Two
- * abstract methods: connect and disconnect are used to act on the
- * underlying bearer code. Connect is called when we have a NetworkRequest
- * and our score is better than the current handling network's score, while
- * disconnect is used when ConnectivityService requests a disconnect.
+ * A Utility class for handling for communicating between bearer-specific
+ * code and ConnectivityService.
*
* A bearer may have more than one NetworkAgent if it can simultaneously
* support separate networks (IMS / Internet / MMS Apns on cellular, or
- * perhaps connections with different SSID or P2P for Wi-Fi). The bearer
- * code should pass its NetworkAgents the NetworkRequests each NetworkAgent
- * can handle, demultiplexing for different network types. The bearer code
- * can also filter out requests it can never handle.
+ * perhaps connections with different SSID or P2P for Wi-Fi).
*
- * Each NetworkAgent needs to be given a score and NetworkCapabilities for
- * their potential network. While disconnected, the NetworkAgent will check
- * each time its score changes or a NetworkRequest changes to see if
- * the NetworkAgent can provide a higher scored network for a NetworkRequest
- * that the NetworkAgent's NetworkCapabilties can satisfy. This condition will
- * trigger a connect request via connect(). After connection, connection data
- * should be given to the NetworkAgent by the bearer, including LinkProperties
- * NetworkCapabilties and NetworkInfo. After that the NetworkAgent will register
- * with ConnectivityService and forward the data on.
* @hide
*/
public abstract class NetworkAgent extends Handler {
- private final SparseArray<NetworkRequestAndScore> mNetworkRequests = new SparseArray<>();
- private boolean mConnectionRequested = false;
-
- private AsyncChannel mAsyncChannel;
+ private volatile AsyncChannel mAsyncChannel;
private final String LOG_TAG;
private static final boolean DBG = true;
private static final boolean VDBG = true;
- // TODO - this class shouldn't cache data or it runs the risk of getting out of sync
- // Make the API require each of these when any is updated so we have the data we need,
- // without caching.
- private LinkProperties mLinkProperties;
- private NetworkInfo mNetworkInfo;
- private NetworkCapabilities mNetworkCapabilities;
- private int mNetworkScore;
- private boolean mRegistered = false;
private final Context mContext;
- private AtomicBoolean mHasRequests = new AtomicBoolean(false);
-
- // TODO - add a name member for logging purposes.
-
- protected final Object mLockObj = new Object();
-
+ private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>();
private static final int BASE = Protocol.BASE_NETWORK_AGENT;
/**
- * Sent by self to queue up a new/modified request.
- * obj = NetworkRequestAndScore
- */
- private static final int CMD_ADD_REQUEST = BASE + 1;
-
- /**
- * Sent by self to queue up the removal of a request.
- * obj = NetworkRequest
- */
- private static final int CMD_REMOVE_REQUEST = BASE + 2;
-
- /**
* Sent by ConnectivityService to the NetworkAgent to inform it of
* suspected connectivity problems on its network. The NetworkAgent
* should take steps to verify and correct connectivity.
*/
- public static final int CMD_SUSPECT_BAD = BASE + 3;
+ public static final int CMD_SUSPECT_BAD = BASE;
/**
* Sent by the NetworkAgent (note the EVENT vs CMD prefix) to
@@ -110,84 +64,63 @@ public abstract class NetworkAgent extends Handler {
* Sent when the NetworkInfo changes, mainly due to change of state.
* obj = NetworkInfo
*/
- public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 4;
+ public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 1;
/**
* Sent by the NetworkAgent to ConnectivityService to pass the current
* NetworkCapabilties.
* obj = NetworkCapabilities
*/
- public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 5;
+ public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 2;
/**
* Sent by the NetworkAgent to ConnectivityService to pass the current
* NetworkProperties.
* obj = NetworkProperties
*/
- public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 6;
+ public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3;
/**
* Sent by the NetworkAgent to ConnectivityService to pass the current
* network score.
- * arg1 = network score int
+ * obj = network score Integer
*/
- public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 7;
+ public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4;
- public NetworkAgent(Looper looper, Context context, String logTag) {
+ public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
+ NetworkCapabilities nc, LinkProperties lp, int score) {
super(looper);
LOG_TAG = logTag;
mContext = context;
- }
-
- /**
- * When conditions are right, register with ConnectivityService.
- * Connditions include having a well defined network and a request
- * that justifies it. The NetworkAgent will remain registered until
- * disconnected.
- * TODO - this should have all data passed in rather than caching
- */
- private void registerSelf() {
- synchronized(mLockObj) {
- if (!mRegistered && mConnectionRequested &&
- mNetworkInfo != null && mNetworkInfo.isConnected() &&
- mNetworkCapabilities != null &&
- mLinkProperties != null &&
- mNetworkScore != 0) {
- if (DBG) log("Registering NetworkAgent");
- mRegistered = true;
- ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
- Context.CONNECTIVITY_SERVICE);
- cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(mNetworkInfo),
- new LinkProperties(mLinkProperties),
- new NetworkCapabilities(mNetworkCapabilities), mNetworkScore);
- } else if (DBG && !mRegistered) {
- String err = "Not registering due to ";
- if (mConnectionRequested == false) err += "no Connect requested ";
- if (mNetworkInfo == null) err += "null NetworkInfo ";
- if (mNetworkInfo != null && mNetworkInfo.isConnected() == false) {
- err += "NetworkInfo disconnected ";
- }
- if (mLinkProperties == null) err += "null LinkProperties ";
- if (mNetworkCapabilities == null) err += "null NetworkCapabilities ";
- if (mNetworkScore == 0) err += "null NetworkScore";
- log(err);
- }
+ if (ni == null || nc == null || lp == null) {
+ throw new IllegalArgumentException();
}
+
+ if (DBG) log("Registering NetworkAgent");
+ ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),
+ new LinkProperties(lp), new NetworkCapabilities(nc), score);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
- synchronized (mLockObj) {
- if (mAsyncChannel != null) {
- log("Received new connection while already connected!");
- } else {
- if (DBG) log("NetworkAgent fully connected");
- mAsyncChannel = new AsyncChannel();
- mAsyncChannel.connected(null, this, msg.replyTo);
- mAsyncChannel.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
- AsyncChannel.STATUS_SUCCESSFUL);
+ if (mAsyncChannel != null) {
+ log("Received new connection while already connected!");
+ } else {
+ if (DBG) log("NetworkAgent fully connected");
+ AsyncChannel ac = new AsyncChannel();
+ ac.connected(null, this, msg.replyTo);
+ ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+ AsyncChannel.STATUS_SUCCESSFUL);
+ synchronized (mPreConnectedQueue) {
+ mAsyncChannel = ac;
+ for (Message m : mPreConnectedQueue) {
+ ac.sendMessage(m);
+ }
+ mPreConnectedQueue.clear();
}
}
break;
@@ -199,213 +132,69 @@ public abstract class NetworkAgent extends Handler {
}
case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
if (DBG) log("NetworkAgent channel lost");
- disconnect();
- clear();
+ // let the client know CS is done with us.
+ unwanted();
+ synchronized (mPreConnectedQueue) {
+ mAsyncChannel = null;
+ }
break;
}
case CMD_SUSPECT_BAD: {
log("Unhandled Message " + msg);
break;
}
- case CMD_ADD_REQUEST: {
- handleAddRequest(msg);
- break;
- }
- case CMD_REMOVE_REQUEST: {
- handleRemoveRequest(msg);
- break;
- }
- }
- }
-
- private void clear() {
- synchronized(mLockObj) {
- mNetworkRequests.clear();
- mHasRequests.set(false);
- mConnectionRequested = false;
- mAsyncChannel = null;
- mRegistered = false;
- }
- }
-
- private static class NetworkRequestAndScore {
- NetworkRequest req;
- int score;
-
- NetworkRequestAndScore(NetworkRequest networkRequest, int score) {
- req = networkRequest;
- this.score = score;
}
}
- private void handleAddRequest(Message msg) {
- NetworkRequestAndScore n = (NetworkRequestAndScore)msg.obj;
- // replaces old request, updating score
- mNetworkRequests.put(n.req.requestId, n);
- mHasRequests.set(true);
- evalScores();
- }
-
- private void handleRemoveRequest(Message msg) {
- NetworkRequest networkRequest = (NetworkRequest)msg.obj;
-
- if (mNetworkRequests.get(networkRequest.requestId) != null) {
- mNetworkRequests.remove(networkRequest.requestId);
- if (mNetworkRequests.size() == 0) mHasRequests.set(false);
- evalScores();
- }
- }
-
- /**
- * Called to go through our list of requests and see if we're
- * good enough to try connecting, or if we have gotten worse and
- * need to disconnect.
- *
- * Once we are registered, does nothing: we disconnect when requested via
- * CMD_CHANNEL_DISCONNECTED, generated by either a loss of connection
- * between modules (bearer or ConnectivityService dies) or more commonly
- * when the NetworkInfo reports to ConnectivityService it is disconnected.
- */
- private void evalScores() {
- synchronized(mLockObj) {
- if (mRegistered) {
- if (VDBG) log("evalScores - already connected - size=" + mNetworkRequests.size());
- // already trying
- return;
- }
- if (VDBG) log("evalScores!");
- for (int i=0; i < mNetworkRequests.size(); i++) {
- int score = mNetworkRequests.valueAt(i).score;
- if (VDBG) log(" checking request Min " + score + " vs my score " + mNetworkScore);
- if (score < mNetworkScore) {
- // have a request that has a lower scored network servicing it
- // (or no network) than we could provide, so let's connect!
- mConnectionRequested = true;
- connect();
- return;
- }
- }
- // Our score is not high enough to satisfy any current request.
- // This can happen if our score goes down after a connection is
- // requested but before we actually connect. In this case, disconnect
- // rather than continue trying - there's no point connecting if we know
- // we'll just be torn down as soon as we do.
- if (mConnectionRequested) {
- mConnectionRequested = false;
- disconnect();
+ private void queueOrSendMessage(int what, Object obj) {
+ synchronized (mPreConnectedQueue) {
+ if (mAsyncChannel != null) {
+ mAsyncChannel.sendMessage(what, obj);
+ } else {
+ Message msg = Message.obtain();
+ msg.what = what;
+ msg.obj = obj;
+ mPreConnectedQueue.add(msg);
}
}
}
- public void addNetworkRequest(NetworkRequest networkRequest, int score) {
- if (DBG) log("adding NetworkRequest " + networkRequest + " with score " + score);
- sendMessage(obtainMessage(CMD_ADD_REQUEST,
- new NetworkRequestAndScore(networkRequest, score)));
- }
-
- public void removeNetworkRequest(NetworkRequest networkRequest) {
- if (DBG) log("removing NetworkRequest " + networkRequest);
- sendMessage(obtainMessage(CMD_REMOVE_REQUEST, networkRequest));
- }
-
/**
* Called by the bearer code when it has new LinkProperties data.
- * If we're a registered NetworkAgent, this new data will get forwarded on,
- * otherwise we store a copy in anticipation of registering. This call
- * may also prompt registration if it causes the NetworkAgent to meet
- * the conditions (fully configured, connected, satisfys a request and
- * has sufficient score).
*/
public void sendLinkProperties(LinkProperties linkProperties) {
- linkProperties = new LinkProperties(linkProperties);
- synchronized(mLockObj) {
- mLinkProperties = linkProperties;
- if (mAsyncChannel != null) {
- mAsyncChannel.sendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, linkProperties);
- } else {
- registerSelf();
- }
- }
+ queueOrSendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, new LinkProperties(linkProperties));
}
/**
* Called by the bearer code when it has new NetworkInfo data.
- * If we're a registered NetworkAgent, this new data will get forwarded on,
- * otherwise we store a copy in anticipation of registering. This call
- * may also prompt registration if it causes the NetworkAgent to meet
- * the conditions (fully configured, connected, satisfys a request and
- * has sufficient score).
*/
public void sendNetworkInfo(NetworkInfo networkInfo) {
- networkInfo = new NetworkInfo(networkInfo);
- synchronized(mLockObj) {
- mNetworkInfo = networkInfo;
- if (mAsyncChannel != null) {
- mAsyncChannel.sendMessage(EVENT_NETWORK_INFO_CHANGED, networkInfo);
- } else {
- registerSelf();
- }
- }
+ queueOrSendMessage(EVENT_NETWORK_INFO_CHANGED, new NetworkInfo(networkInfo));
}
/**
* Called by the bearer code when it has new NetworkCapabilities data.
- * If we're a registered NetworkAgent, this new data will get forwarded on,
- * otherwise we store a copy in anticipation of registering. This call
- * may also prompt registration if it causes the NetworkAgent to meet
- * the conditions (fully configured, connected, satisfys a request and
- * has sufficient score).
- * Note that if these capabilities make the network non-useful,
- * ConnectivityServce will tear this network down.
*/
public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) {
- networkCapabilities = new NetworkCapabilities(networkCapabilities);
- synchronized(mLockObj) {
- mNetworkCapabilities = networkCapabilities;
- if (mAsyncChannel != null) {
- mAsyncChannel.sendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED, networkCapabilities);
- } else {
- registerSelf();
- }
- }
- }
-
- public NetworkCapabilities getNetworkCapabilities() {
- synchronized(mLockObj) {
- return new NetworkCapabilities(mNetworkCapabilities);
- }
+ queueOrSendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED,
+ new NetworkCapabilities(networkCapabilities));
}
/**
* Called by the bearer code when it has a new score for this network.
- * If we're a registered NetworkAgent, this new data will get forwarded on,
- * otherwise we store a copy.
*/
- public synchronized void sendNetworkScore(int score) {
- synchronized(mLockObj) {
- mNetworkScore = score;
- evalScores();
- if (mAsyncChannel != null) {
- mAsyncChannel.sendMessage(EVENT_NETWORK_SCORE_CHANGED, mNetworkScore);
- } else {
- registerSelf();
- }
- }
- }
-
- public boolean hasRequests() {
- return mHasRequests.get();
+ public void sendNetworkScore(int score) {
+ queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, new Integer(score));
}
- public boolean isConnectionRequested() {
- synchronized(mLockObj) {
- return mConnectionRequested;
- }
- }
-
-
- abstract protected void connect();
- abstract protected void disconnect();
+ /**
+ * Called when ConnectivityService has indicated they no longer want this network.
+ * The parent factory should (previously) have received indication of the change
+ * as well, either canceling NetworkRequests or altering their score such that this
+ * network won't be immediately requested again.
+ */
+ abstract protected void unwanted();
protected void log(String s) {
Log.d(LOG_TAG, "NetworkAgent: " + s);
diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java
new file mode 100644
index 0000000..a20e8e7
--- /dev/null
+++ b/core/java/android/net/NetworkFactory.java
@@ -0,0 +1,275 @@
+/*
+ * 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.net;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+
+/**
+ * A NetworkFactory is an entity that creates NetworkAgent objects.
+ * The bearers register with ConnectivityService using {@link #register} and
+ * their factory will start receiving scored NetworkRequests. NetworkRequests
+ * can be filtered 3 ways: by NetworkCapabilities, by score and more complexly by
+ * overridden function. All of these can be dynamic - changing NetworkCapabilities
+ * or score forces re-evaluation of all current requests.
+ *
+ * If any requests pass the filter some overrideable functions will be called.
+ * If the bearer only cares about very simple start/stopNetwork callbacks, those
+ * functions can be overridden. If the bearer needs more interaction, it can
+ * override addNetworkRequest and removeNetworkRequest which will give it each
+ * request that passes their current filters.
+ * @hide
+ **/
+public class NetworkFactory extends Handler {
+ private static final boolean DBG = true;
+
+ private static final int BASE = Protocol.BASE_NETWORK_FACTORY;
+ /**
+ * Pass a network request to the bearer. If the bearer believes it can
+ * satisfy the request it should connect to the network and create a
+ * NetworkAgent. Once the NetworkAgent is fully functional it will
+ * register itself with ConnectivityService using registerNetworkAgent.
+ * If the bearer cannot immediately satisfy the request (no network,
+ * user disabled the radio, lower-scored network) it should remember
+ * any NetworkRequests it may be able to satisfy in the future. It may
+ * disregard any that it will never be able to service, for example
+ * those requiring a different bearer.
+ * msg.obj = NetworkRequest
+ * msg.arg1 = score - the score of the any network currently satisfying this
+ * request. If this bearer knows in advance it cannot
+ * exceed this score it should not try to connect, holding the request
+ * for the future.
+ * Note that subsequent events may give a different (lower
+ * or higher) score for this request, transmitted to each
+ * NetworkFactory through additional CMD_REQUEST_NETWORK msgs
+ * with the same NetworkRequest but an updated score.
+ * Also, network conditions may change for this bearer
+ * allowing for a better score in the future.
+ */
+ public static final int CMD_REQUEST_NETWORK = BASE;
+
+ /**
+ * Cancel a network request
+ * msg.obj = NetworkRequest
+ */
+ public static final int CMD_CANCEL_REQUEST = BASE + 1;
+
+ /**
+ * Internally used to set our best-guess score.
+ * msg.arg1 = new score
+ */
+ private static final int CMD_SET_SCORE = BASE + 2;
+
+ /**
+ * Internally used to set our current filter for coarse bandwidth changes with
+ * technology changes.
+ * msg.obj = new filter
+ */
+ private static final int CMD_SET_FILTER = BASE + 3;
+
+ private final Context mContext;
+ private final String LOG_TAG;
+
+ private final SparseArray<NetworkRequestInfo> mNetworkRequests =
+ new SparseArray<NetworkRequestInfo>();
+
+ private int mScore;
+ private NetworkCapabilities mCapabilityFilter;
+
+ private int mRefCount = 0;
+ private Messenger mMessenger = null;
+
+ public NetworkFactory(Looper looper, Context context, String logTag,
+ NetworkCapabilities filter) {
+ super(looper);
+ LOG_TAG = logTag;
+ mContext = context;
+ mCapabilityFilter = filter;
+ }
+
+ public void register() {
+ if (DBG) log("Registering NetworkFactory");
+ if (mMessenger == null) {
+ mMessenger = new Messenger(this);
+ ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger, LOG_TAG);
+ }
+ }
+
+ public void unregister() {
+ if (DBG) log("Unregistering NetworkFactory");
+ if (mMessenger != null) {
+ ConnectivityManager.from(mContext).unregisterNetworkFactory(mMessenger);
+ mMessenger = null;
+ }
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case CMD_REQUEST_NETWORK: {
+ handleAddRequest((NetworkRequest)msg.obj, msg.arg1);
+ break;
+ }
+ case CMD_CANCEL_REQUEST: {
+ handleRemoveRequest((NetworkRequest) msg.obj);
+ break;
+ }
+ case CMD_SET_SCORE: {
+ handleSetScore(msg.arg1);
+ break;
+ }
+ case CMD_SET_FILTER: {
+ handleSetFilter((NetworkCapabilities) msg.obj);
+ break;
+ }
+ }
+ }
+
+ private class NetworkRequestInfo {
+ public final NetworkRequest request;
+ public int score;
+ public boolean requested; // do we have a request outstanding, limited by score
+
+ public NetworkRequestInfo(NetworkRequest request, int score) {
+ this.request = request;
+ this.score = score;
+ this.requested = false;
+ }
+ }
+
+ private void handleAddRequest(NetworkRequest request, int score) {
+ NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
+ if (n == null) {
+ n = new NetworkRequestInfo(request, score);
+ mNetworkRequests.put(n.request.requestId, n);
+ } else {
+ n.score = score;
+ }
+ if (DBG) log("got request " + request + " with score " + score);
+ if (DBG) log(" my score=" + mScore + ", my filter=" + mCapabilityFilter);
+
+ evalRequest(n);
+ }
+
+ private void handleRemoveRequest(NetworkRequest request) {
+ NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
+ if (n != null && n.requested) {
+ mNetworkRequests.remove(request.requestId);
+ releaseNetworkFor(n.request);
+ }
+ }
+
+ private void handleSetScore(int score) {
+ mScore = score;
+ evalRequests();
+ }
+
+ private void handleSetFilter(NetworkCapabilities netCap) {
+ mCapabilityFilter = netCap;
+ evalRequests();
+ }
+
+ /**
+ * Overridable function to provide complex filtering.
+ * Called for every request every time a new NetworkRequest is seen
+ * and whenever the filterScore or filterNetworkCapabilities change.
+ *
+ * acceptRequest can be overriden to provide complex filter behavior
+ * for the incoming requests
+ *
+ * For output, this class will call {@link #needNetworkFor} and
+ * {@link #releaseNetworkFor} for every request that passes the filters.
+ * If you don't need to see every request, you can leave the base
+ * implementations of those two functions and instead override
+ * {@link #startNetwork} and {@link #stopNetwork}.
+ *
+ * If you want to see every score fluctuation on every request, set
+ * your score filter to a very high number and watch {@link #needNetworkFor}.
+ *
+ * @return {@code true} to accept the request.
+ */
+ public boolean acceptRequest(NetworkRequest request, int score) {
+ return true;
+ }
+
+ private void evalRequest(NetworkRequestInfo n) {
+ if (n.requested == false && n.score < mScore &&
+ n.request.networkCapabilities.satisfiedByNetworkCapabilities(
+ mCapabilityFilter) && acceptRequest(n.request, n.score)) {
+ needNetworkFor(n.request, n.score);
+ n.requested = true;
+ } else if (n.requested == true &&
+ (n.score > mScore || n.request.networkCapabilities.satisfiedByNetworkCapabilities(
+ mCapabilityFilter) == false || acceptRequest(n.request, n.score) == false)) {
+ releaseNetworkFor(n.request);
+ n.requested = false;
+ }
+ }
+
+ private void evalRequests() {
+ for (int i = 0; i < mNetworkRequests.size(); i++) {
+ NetworkRequestInfo n = mNetworkRequests.valueAt(i);
+
+ evalRequest(n);
+ }
+ }
+
+ // override to do simple mode (request independent)
+ protected void startNetwork() { }
+ protected void stopNetwork() { }
+
+ // override to do fancier stuff
+ protected void needNetworkFor(NetworkRequest networkRequest, int score) {
+ if (++mRefCount == 1) startNetwork();
+ }
+
+ protected void releaseNetworkFor(NetworkRequest networkRequest) {
+ if (--mRefCount == 0) stopNetwork();
+ }
+
+
+ public void addNetworkRequest(NetworkRequest networkRequest, int score) {
+ sendMessage(obtainMessage(CMD_REQUEST_NETWORK,
+ new NetworkRequestInfo(networkRequest, score)));
+ }
+
+ public void removeNetworkRequest(NetworkRequest networkRequest) {
+ sendMessage(obtainMessage(CMD_CANCEL_REQUEST, networkRequest));
+ }
+
+ public void setScoreFilter(int score) {
+ sendMessage(obtainMessage(CMD_SET_SCORE, score, 0));
+ }
+
+ public void setCapabilityFilter(NetworkCapabilities netCap) {
+ sendMessage(obtainMessage(CMD_SET_FILTER, new NetworkCapabilities(netCap)));
+ }
+
+ protected void log(String s) {
+ Log.d(LOG_TAG, s);
+ }
+}
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 9e656ee..d279412 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -188,6 +188,15 @@ public class NetworkInfo implements Parcelable {
}
/**
+ * @hide
+ */
+ public void setType(int type) {
+ synchronized (this) {
+ mNetworkType = type;
+ }
+ }
+
+ /**
* Return a network-type-specific integer describing the subtype
* of the network.
* @return the network subtype
@@ -198,7 +207,10 @@ public class NetworkInfo implements Parcelable {
}
}
- void setSubtype(int subtype, String subtypeName) {
+ /**
+ * @hide
+ */
+ public void setSubtype(int subtype, String subtypeName) {
synchronized (this) {
mSubtype = subtype;
mSubtypeName = subtypeName;
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 480cb05..47377e9 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -47,19 +47,19 @@ public class NetworkRequest implements Parcelable {
public final int requestId;
/**
- * Set for legacy requests and the default.
+ * Set for legacy requests and the default. Set to TYPE_NONE for none.
* Causes CONNECTIVITY_ACTION broadcasts to be sent.
* @hide
*/
- public final boolean needsBroadcasts;
+ public final int legacyType;
/**
* @hide
*/
- public NetworkRequest(NetworkCapabilities nc, boolean needsBroadcasts, int rId) {
+ public NetworkRequest(NetworkCapabilities nc, int legacyType, int rId) {
requestId = rId;
networkCapabilities = nc;
- this.needsBroadcasts = needsBroadcasts;
+ this.legacyType = legacyType;
}
/**
@@ -68,7 +68,7 @@ public class NetworkRequest implements Parcelable {
public NetworkRequest(NetworkRequest that) {
networkCapabilities = new NetworkCapabilities(that.networkCapabilities);
requestId = that.requestId;
- needsBroadcasts = that.needsBroadcasts;
+ this.legacyType = that.legacyType;
}
// implement the Parcelable interface
@@ -77,16 +77,16 @@ public class NetworkRequest implements Parcelable {
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeParcelable(networkCapabilities, flags);
- dest.writeInt(needsBroadcasts ? 1 : 0);
+ dest.writeInt(legacyType);
dest.writeInt(requestId);
}
public static final Creator<NetworkRequest> CREATOR =
new Creator<NetworkRequest>() {
public NetworkRequest createFromParcel(Parcel in) {
NetworkCapabilities nc = (NetworkCapabilities)in.readParcelable(null);
- boolean needsBroadcasts = (in.readInt() == 1);
+ int legacyType = in.readInt();
int requestId = in.readInt();
- NetworkRequest result = new NetworkRequest(nc, needsBroadcasts, requestId);
+ NetworkRequest result = new NetworkRequest(nc, legacyType, requestId);
return result;
}
public NetworkRequest[] newArray(int size) {
@@ -95,14 +95,14 @@ public class NetworkRequest implements Parcelable {
};
public String toString() {
- return "NetworkRequest [ id=" + requestId + ", needsBroadcasts=" + needsBroadcasts +
+ return "NetworkRequest [ id=" + requestId + ", legacyType=" + legacyType +
", " + networkCapabilities.toString() + " ]";
}
public boolean equals(Object obj) {
if (obj instanceof NetworkRequest == false) return false;
NetworkRequest that = (NetworkRequest)obj;
- return (that.needsBroadcasts == this.needsBroadcasts &&
+ return (that.legacyType == this.legacyType &&
that.requestId == this.requestId &&
((that.networkCapabilities == null && this.networkCapabilities == null) ||
(that.networkCapabilities != null &&
@@ -110,7 +110,7 @@ public class NetworkRequest implements Parcelable {
}
public int hashCode() {
- return requestId + (needsBroadcasts ? 1013 : 2026) +
+ return requestId + (legacyType * 1013) +
(networkCapabilities.hashCode() * 1051);
}
}
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/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 424d860..5056097 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -75,22 +75,10 @@ class GLES20Canvas extends HardwareCanvas {
// Constructors
///////////////////////////////////////////////////////////////////////////
- /**
- * Creates a canvas to render directly on screen.
- */
- GLES20Canvas(boolean translucent) {
- this(false, translucent);
- }
-
- protected GLES20Canvas(boolean record, boolean translucent) {
- mOpaque = !translucent;
-
- if (record) {
- mRenderer = nCreateDisplayListRenderer();
- } else {
- mRenderer = nCreateRenderer();
- }
-
+ // TODO: Merge with GLES20RecordingCanvas
+ protected GLES20Canvas() {
+ mOpaque = false;
+ mRenderer = nCreateDisplayListRenderer();
setupFinalizer();
}
@@ -102,7 +90,6 @@ class GLES20Canvas extends HardwareCanvas {
}
}
- private static native long nCreateRenderer();
private static native long nCreateDisplayListRenderer();
private static native void nResetDisplayListRenderer(long renderer);
private static native void nDestroyRenderer(long renderer);
@@ -131,36 +118,6 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nSetProperty(String name, String value);
///////////////////////////////////////////////////////////////////////////
- // Hardware layers
- ///////////////////////////////////////////////////////////////////////////
-
- @Override
- void pushLayerUpdate(HardwareLayer layer) {
- nPushLayerUpdate(mRenderer, layer.getLayer());
- }
-
- @Override
- void cancelLayerUpdate(HardwareLayer layer) {
- nCancelLayerUpdate(mRenderer, layer.getLayer());
- }
-
- @Override
- void flushLayerUpdates() {
- nFlushLayerUpdates(mRenderer);
- }
-
- @Override
- void clearLayerUpdates() {
- nClearLayerUpdates(mRenderer);
- }
-
- static native boolean nCopyLayer(long layerId, long bitmap);
- private static native void nClearLayerUpdates(long renderer);
- private static native void nFlushLayerUpdates(long renderer);
- private static native void nPushLayerUpdate(long renderer, long layer);
- private static native void nCancelLayerUpdate(long renderer, long layer);
-
- ///////////////////////////////////////////////////////////////////////////
// Canvas management
///////////////////////////////////////////////////////////////////////////
@@ -234,20 +191,6 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nFinish(long renderer);
- /**
- * Returns the size of the stencil buffer required by the underlying
- * implementation.
- *
- * @return The minimum number of bits the stencil buffer must. Always >= 0.
- *
- * @hide
- */
- public static int getStencilSize() {
- return nGetStencilSize();
- }
-
- private static native int nGetStencilSize();
-
///////////////////////////////////////////////////////////////////////////
// Functor
///////////////////////////////////////////////////////////////////////////
@@ -284,49 +227,6 @@ class GLES20Canvas extends HardwareCanvas {
*/
static final int FLUSH_CACHES_FULL = 2;
- /**
- * Flush caches to reclaim as much memory as possible. The amount of memory
- * to reclaim is indicate by the level parameter.
- *
- * The level can be one of {@link #FLUSH_CACHES_MODERATE} or
- * {@link #FLUSH_CACHES_FULL}.
- *
- * @param level Hint about the amount of memory to reclaim
- */
- static void flushCaches(int level) {
- nFlushCaches(level);
- }
-
- private static native void nFlushCaches(int level);
-
- /**
- * Release all resources associated with the underlying caches. This should
- * only be called after a full flushCaches().
- *
- * @hide
- */
- static void terminateCaches() {
- nTerminateCaches();
- }
-
- private static native void nTerminateCaches();
-
- static boolean initCaches() {
- return nInitCaches();
- }
-
- private static native boolean nInitCaches();
-
- ///////////////////////////////////////////////////////////////////////////
- // Atlas
- ///////////////////////////////////////////////////////////////////////////
-
- static void initAtlas(GraphicBuffer buffer, long[] map) {
- nInitAtlas(buffer, map, map.length);
- }
-
- private static native void nInitAtlas(GraphicBuffer buffer, long[] map, int count);
-
///////////////////////////////////////////////////////////////////////////
// Display list
///////////////////////////////////////////////////////////////////////////
@@ -899,12 +799,6 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nDrawPath(long renderer, long path, long paint);
private static native void nDrawRects(long renderer, long region, long paint);
- void drawRects(float[] rects, int count, Paint paint) {
- nDrawRects(mRenderer, rects, count, paint.mNativePaint);
- }
-
- private static native void nDrawRects(long renderer, float[] rects, int count, long paint);
-
@Override
public void drawPicture(Picture picture) {
if (picture.createdFromStream) {
diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java
index a94ec3a..b2961e5 100644
--- a/core/java/android/view/GLES20RecordingCanvas.java
+++ b/core/java/android/view/GLES20RecordingCanvas.java
@@ -36,7 +36,7 @@ class GLES20RecordingCanvas extends GLES20Canvas {
RenderNode mNode;
private GLES20RecordingCanvas() {
- super(true, true);
+ super();
}
static GLES20RecordingCanvas obtain(@NonNull RenderNode node) {
diff --git a/core/java/android/view/GLRenderer.java b/core/java/android/view/GLRenderer.java
deleted file mode 100644
index f1163e2..0000000
--- a/core/java/android/view/GLRenderer.java
+++ /dev/null
@@ -1,1521 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import static javax.microedition.khronos.egl.EGL10.EGL_ALPHA_SIZE;
-import static javax.microedition.khronos.egl.EGL10.EGL_BAD_NATIVE_WINDOW;
-import static javax.microedition.khronos.egl.EGL10.EGL_BLUE_SIZE;
-import static javax.microedition.khronos.egl.EGL10.EGL_CONFIG_CAVEAT;
-import static javax.microedition.khronos.egl.EGL10.EGL_DEFAULT_DISPLAY;
-import static javax.microedition.khronos.egl.EGL10.EGL_DEPTH_SIZE;
-import static javax.microedition.khronos.egl.EGL10.EGL_DRAW;
-import static javax.microedition.khronos.egl.EGL10.EGL_GREEN_SIZE;
-import static javax.microedition.khronos.egl.EGL10.EGL_HEIGHT;
-import static javax.microedition.khronos.egl.EGL10.EGL_NONE;
-import static javax.microedition.khronos.egl.EGL10.EGL_NO_CONTEXT;
-import static javax.microedition.khronos.egl.EGL10.EGL_NO_DISPLAY;
-import static javax.microedition.khronos.egl.EGL10.EGL_NO_SURFACE;
-import static javax.microedition.khronos.egl.EGL10.EGL_RED_SIZE;
-import static javax.microedition.khronos.egl.EGL10.EGL_RENDERABLE_TYPE;
-import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLES;
-import static javax.microedition.khronos.egl.EGL10.EGL_SAMPLE_BUFFERS;
-import static javax.microedition.khronos.egl.EGL10.EGL_STENCIL_SIZE;
-import static javax.microedition.khronos.egl.EGL10.EGL_SUCCESS;
-import static javax.microedition.khronos.egl.EGL10.EGL_SURFACE_TYPE;
-import static javax.microedition.khronos.egl.EGL10.EGL_WIDTH;
-import static javax.microedition.khronos.egl.EGL10.EGL_WINDOW_BIT;
-
-import android.content.ComponentCallbacks2;
-import android.graphics.Bitmap;
-import android.graphics.Paint;
-import android.graphics.Rect;
-import android.graphics.SurfaceTexture;
-import android.opengl.EGL14;
-import android.opengl.GLUtils;
-import android.opengl.ManagedEGLContext;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.Trace;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Surface.OutOfResourcesException;
-
-import com.google.android.gles_jni.EGLImpl;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.locks.ReentrantLock;
-
-import javax.microedition.khronos.egl.EGL10;
-import javax.microedition.khronos.egl.EGL11;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.egl.EGLContext;
-import javax.microedition.khronos.egl.EGLDisplay;
-import javax.microedition.khronos.egl.EGLSurface;
-import javax.microedition.khronos.opengles.GL;
-
-/**
- * Hardware renderer using OpenGL
- *
- * @hide
- */
-public class GLRenderer extends HardwareRenderer {
- static final int SURFACE_STATE_ERROR = 0;
- static final int SURFACE_STATE_SUCCESS = 1;
- static final int SURFACE_STATE_UPDATED = 2;
-
- static final int FUNCTOR_PROCESS_DELAY = 4;
-
- /**
- * Number of frames to profile.
- */
- private static final int PROFILE_MAX_FRAMES = 128;
-
- /**
- * Number of floats per profiled frame.
- */
- private static final int PROFILE_FRAME_DATA_COUNT = 3;
-
- private static final int PROFILE_DRAW_MARGIN = 0;
- private static final int PROFILE_DRAW_WIDTH = 3;
- private static final int[] PROFILE_DRAW_COLORS = { 0xcf3e66cc, 0xcfdc3912, 0xcfe69800 };
- private static final int PROFILE_DRAW_CURRENT_FRAME_COLOR = 0xcf5faa4d;
- private static final int PROFILE_DRAW_THRESHOLD_COLOR = 0xff5faa4d;
- private static final int PROFILE_DRAW_THRESHOLD_STROKE_WIDTH = 2;
- private static final int PROFILE_DRAW_DP_PER_MS = 7;
-
- private static final String[] VISUALIZERS = {
- PROFILE_PROPERTY_VISUALIZE_BARS,
- };
-
- private static final String[] OVERDRAW = {
- OVERDRAW_PROPERTY_SHOW,
- };
- private static final int GL_VERSION = 2;
-
- static EGL10 sEgl;
- static EGLDisplay sEglDisplay;
- static EGLConfig sEglConfig;
- static final Object[] sEglLock = new Object[0];
- int mWidth = -1, mHeight = -1;
-
- static final ThreadLocal<ManagedEGLContext> sEglContextStorage
- = new ThreadLocal<ManagedEGLContext>();
-
- EGLContext mEglContext;
- Thread mEglThread;
-
- EGLSurface mEglSurface;
-
- GL mGl;
- HardwareCanvas mCanvas;
-
- String mName;
-
- long mFrameCount;
- Paint mDebugPaint;
-
- static boolean sDirtyRegions;
- static final boolean sDirtyRegionsRequested;
- static {
- String dirtyProperty = SystemProperties.get(RENDER_DIRTY_REGIONS_PROPERTY, "true");
- //noinspection PointlessBooleanExpression,ConstantConditions
- sDirtyRegions = "true".equalsIgnoreCase(dirtyProperty);
- sDirtyRegionsRequested = sDirtyRegions;
- }
-
- boolean mDirtyRegionsEnabled;
- boolean mUpdateDirtyRegions;
-
- boolean mProfileEnabled;
- int mProfileVisualizerType = -1;
- float[] mProfileData;
- ReentrantLock mProfileLock;
- int mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT;
-
- GraphDataProvider mDebugDataProvider;
- float[][] mProfileShapes;
- Paint mProfilePaint;
-
- boolean mDebugDirtyRegions;
- int mDebugOverdraw = -1;
-
- final boolean mTranslucent;
-
- private boolean mDestroyed;
-
- private final Rect mRedrawClip = new Rect();
-
- private final int[] mSurfaceSize = new int[2];
-
- private long mDrawDelta = Long.MAX_VALUE;
-
- private GLES20Canvas mGlCanvas;
-
- private DisplayMetrics mDisplayMetrics;
-
- private static EGLSurface sPbuffer;
- private static final Object[] sPbufferLock = new Object[0];
-
- private List<HardwareLayer> mLayerUpdates = new ArrayList<HardwareLayer>();
-
- private static class GLRendererEglContext extends ManagedEGLContext {
- final Handler mHandler = new Handler();
-
- public GLRendererEglContext(EGLContext context) {
- super(context);
- }
-
- @Override
- public void onTerminate(final EGLContext eglContext) {
- // Make sure we do this on the correct thread.
- if (mHandler.getLooper() != Looper.myLooper()) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- onTerminate(eglContext);
- }
- });
- return;
- }
-
- synchronized (sEglLock) {
- if (sEgl == null) return;
-
- if (EGLImpl.getInitCount(sEglDisplay) == 1) {
- usePbufferSurface(eglContext);
- GLES20Canvas.terminateCaches();
-
- sEgl.eglDestroyContext(sEglDisplay, eglContext);
- sEglContextStorage.set(null);
- sEglContextStorage.remove();
-
- sEgl.eglDestroySurface(sEglDisplay, sPbuffer);
- sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
- EGL_NO_SURFACE, EGL_NO_CONTEXT);
-
- sEgl.eglReleaseThread();
- sEgl.eglTerminate(sEglDisplay);
-
- sEgl = null;
- sEglDisplay = null;
- sEglConfig = null;
- sPbuffer = null;
- }
- }
- }
- }
-
- HardwareCanvas createCanvas() {
- return mGlCanvas = new GLES20Canvas(mTranslucent);
- }
-
- ManagedEGLContext createManagedContext(EGLContext eglContext) {
- return new GLRendererEglContext(mEglContext);
- }
-
- int[] getConfig(boolean dirtyRegions) {
- //noinspection PointlessBooleanExpression,ConstantConditions
- final int stencilSize = GLES20Canvas.getStencilSize();
- final int swapBehavior = dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0;
-
- return new int[] {
- EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
- EGL_RED_SIZE, 8,
- EGL_GREEN_SIZE, 8,
- EGL_BLUE_SIZE, 8,
- EGL_ALPHA_SIZE, 8,
- EGL_DEPTH_SIZE, 0,
- EGL_CONFIG_CAVEAT, EGL_NONE,
- EGL_STENCIL_SIZE, stencilSize,
- EGL_SURFACE_TYPE, EGL_WINDOW_BIT | swapBehavior,
- EGL_NONE
- };
- }
-
- void initCaches() {
- if (GLES20Canvas.initCaches()) {
- // Caches were (re)initialized, rebind atlas
- initAtlas();
- }
- }
-
- void initAtlas() {
- IBinder binder = ServiceManager.getService("assetatlas");
- if (binder == null) return;
-
- IAssetAtlas atlas = IAssetAtlas.Stub.asInterface(binder);
- try {
- if (atlas.isCompatible(android.os.Process.myPpid())) {
- GraphicBuffer buffer = atlas.getBuffer();
- if (buffer != null) {
- long[] map = atlas.getMap();
- if (map != null) {
- GLES20Canvas.initAtlas(buffer, map);
- }
- // If IAssetAtlas is not the same class as the IBinder
- // we are using a remote service and we can safely
- // destroy the graphic buffer
- if (atlas.getClass() != binder.getClass()) {
- buffer.destroy();
- }
- }
- }
- } catch (RemoteException e) {
- Log.w(LOG_TAG, "Could not acquire atlas", e);
- }
- }
-
- boolean canDraw() {
- return mGl != null && mCanvas != null && mGlCanvas != null;
- }
-
- int onPreDraw(Rect dirty) {
- return mGlCanvas.onPreDraw(dirty);
- }
-
- void onPostDraw() {
- mGlCanvas.onPostDraw();
- }
-
- void drawProfileData(View.AttachInfo attachInfo) {
- if (mDebugDataProvider != null) {
- final GraphDataProvider provider = mDebugDataProvider;
- initProfileDrawData(attachInfo, provider);
-
- final int height = provider.getVerticalUnitSize();
- final int margin = provider.getHorizontaUnitMargin();
- final int width = provider.getHorizontalUnitSize();
-
- int x = 0;
- int count = 0;
- int current = 0;
-
- final float[] data = provider.getData();
- final int elementCount = provider.getElementCount();
- final int graphType = provider.getGraphType();
-
- int totalCount = provider.getFrameCount() * elementCount;
- if (graphType == GraphDataProvider.GRAPH_TYPE_LINES) {
- totalCount -= elementCount;
- }
-
- for (int i = 0; i < totalCount; i += elementCount) {
- if (data[i] < 0.0f) break;
-
- int index = count * 4;
- if (i == provider.getCurrentFrame() * elementCount) current = index;
-
- x += margin;
- int x2 = x + width;
-
- int y2 = mHeight;
- int y1 = (int) (y2 - data[i] * height);
-
- switch (graphType) {
- case GraphDataProvider.GRAPH_TYPE_BARS: {
- for (int j = 0; j < elementCount; j++) {
- //noinspection MismatchedReadAndWriteOfArray
- final float[] r = mProfileShapes[j];
- r[index] = x;
- r[index + 1] = y1;
- r[index + 2] = x2;
- r[index + 3] = y2;
-
- y2 = y1;
- if (j < elementCount - 1) {
- y1 = (int) (y2 - data[i + j + 1] * height);
- }
- }
- } break;
- case GraphDataProvider.GRAPH_TYPE_LINES: {
- for (int j = 0; j < elementCount; j++) {
- //noinspection MismatchedReadAndWriteOfArray
- final float[] r = mProfileShapes[j];
- r[index] = (x + x2) * 0.5f;
- r[index + 1] = index == 0 ? y1 : r[index - 1];
- r[index + 2] = r[index] + width;
- r[index + 3] = y1;
-
- y2 = y1;
- if (j < elementCount - 1) {
- y1 = (int) (y2 - data[i + j + 1] * height);
- }
- }
- } break;
- }
-
-
- x += width;
- count++;
- }
-
- x += margin;
-
- drawGraph(graphType, count);
- drawCurrentFrame(graphType, current);
- drawThreshold(x, height);
- }
- }
-
- private void drawGraph(int graphType, int count) {
- for (int i = 0; i < mProfileShapes.length; i++) {
- mDebugDataProvider.setupGraphPaint(mProfilePaint, i);
- switch (graphType) {
- case GraphDataProvider.GRAPH_TYPE_BARS:
- mGlCanvas.drawRects(mProfileShapes[i], count * 4, mProfilePaint);
- break;
- case GraphDataProvider.GRAPH_TYPE_LINES:
- mGlCanvas.drawLines(mProfileShapes[i], 0, count * 4, mProfilePaint);
- break;
- }
- }
- }
-
- private void drawCurrentFrame(int graphType, int index) {
- if (index >= 0) {
- mDebugDataProvider.setupCurrentFramePaint(mProfilePaint);
- switch (graphType) {
- case GraphDataProvider.GRAPH_TYPE_BARS:
- mGlCanvas.drawRect(mProfileShapes[2][index], mProfileShapes[2][index + 1],
- mProfileShapes[2][index + 2], mProfileShapes[0][index + 3],
- mProfilePaint);
- break;
- case GraphDataProvider.GRAPH_TYPE_LINES:
- mGlCanvas.drawLine(mProfileShapes[2][index], mProfileShapes[2][index + 1],
- mProfileShapes[2][index], mHeight, mProfilePaint);
- break;
- }
- }
- }
-
- private void drawThreshold(int x, int height) {
- float threshold = mDebugDataProvider.getThreshold();
- if (threshold > 0.0f) {
- mDebugDataProvider.setupThresholdPaint(mProfilePaint);
- int y = (int) (mHeight - threshold * height);
- mGlCanvas.drawLine(0.0f, y, x, y, mProfilePaint);
- }
- }
-
- private void initProfileDrawData(View.AttachInfo attachInfo, GraphDataProvider provider) {
- if (mProfileShapes == null) {
- final int elementCount = provider.getElementCount();
- final int frameCount = provider.getFrameCount();
-
- mProfileShapes = new float[elementCount][];
- for (int i = 0; i < elementCount; i++) {
- mProfileShapes[i] = new float[frameCount * 4];
- }
-
- mProfilePaint = new Paint();
- }
-
- mProfilePaint.reset();
- if (provider.getGraphType() == GraphDataProvider.GRAPH_TYPE_LINES) {
- mProfilePaint.setAntiAlias(true);
- }
-
- if (mDisplayMetrics == null) {
- mDisplayMetrics = new DisplayMetrics();
- }
-
- attachInfo.mDisplay.getMetrics(mDisplayMetrics);
- provider.prepare(mDisplayMetrics);
- }
-
- @Override
- void destroy(boolean full) {
- try {
- if (full && mCanvas != null) {
- mCanvas = null;
- }
-
- if (!isEnabled() || mDestroyed) {
- setEnabled(false);
- return;
- }
-
- destroySurface();
- setEnabled(false);
-
- mDestroyed = true;
- mGl = null;
- } finally {
- if (full && mGlCanvas != null) {
- mGlCanvas = null;
- }
- }
- }
-
- @Override
- void pushLayerUpdate(HardwareLayer layer) {
- mLayerUpdates.add(layer);
- }
-
- @Override
- void flushLayerUpdates() {
- if (validate()) {
- flushLayerChanges();
- mGlCanvas.flushLayerUpdates();
- }
- }
-
- @Override
- HardwareLayer createTextureLayer() {
- validate();
- return HardwareLayer.createTextureLayer(this);
- }
-
- @Override
- public HardwareLayer createDisplayListLayer(int width, int height) {
- validate();
- return HardwareLayer.createDisplayListLayer(this, width, height);
- }
-
- boolean hasContext() {
- return sEgl != null && mEglContext != null
- && mEglContext.equals(sEgl.eglGetCurrentContext());
- }
-
- @Override
- void onLayerDestroyed(HardwareLayer layer) {
- if (mGlCanvas != null) {
- mGlCanvas.cancelLayerUpdate(layer);
- }
- mLayerUpdates.remove(layer);
- }
-
- @Override
- public SurfaceTexture createSurfaceTexture(HardwareLayer layer) {
- return layer.createSurfaceTexture();
- }
-
- @Override
- boolean copyLayerInto(HardwareLayer layer, Bitmap bitmap) {
- if (!validate()) {
- throw new IllegalStateException("Could not acquire hardware rendering context");
- }
- layer.flushChanges();
- return GLES20Canvas.nCopyLayer(layer.getLayer(), bitmap.mNativeBitmap);
- }
-
- @Override
- boolean safelyRun(Runnable action) {
- boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR;
-
- if (needsContext) {
- GLRendererEglContext managedContext =
- (GLRendererEglContext) sEglContextStorage.get();
- if (managedContext == null) return false;
- usePbufferSurface(managedContext.getContext());
- }
-
- try {
- action.run();
- } finally {
- if (needsContext) {
- sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
- EGL_NO_SURFACE, EGL_NO_CONTEXT);
- }
- }
-
- return true;
- }
-
- @Override
- void invokeFunctor(long functor, boolean waitForCompletion) {
- boolean needsContext = !isEnabled() || checkRenderContext() == SURFACE_STATE_ERROR;
- boolean hasContext = !needsContext;
-
- if (needsContext) {
- GLRendererEglContext managedContext =
- (GLRendererEglContext) sEglContextStorage.get();
- if (managedContext != null) {
- usePbufferSurface(managedContext.getContext());
- hasContext = true;
- }
- }
-
- try {
- nInvokeFunctor(functor, hasContext);
- } finally {
- if (needsContext) {
- sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
- EGL_NO_SURFACE, EGL_NO_CONTEXT);
- }
- }
- }
-
- private static native void nInvokeFunctor(long functor, boolean hasContext);
-
- @Override
- void destroyHardwareResources(final View view) {
- if (view != null) {
- safelyRun(new Runnable() {
- @Override
- public void run() {
- if (mCanvas != null) {
- mCanvas.clearLayerUpdates();
- }
- destroyResources(view);
- GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
- }
- });
- }
- }
-
- private static void destroyResources(View view) {
- view.destroyHardwareResources();
-
- if (view instanceof ViewGroup) {
- ViewGroup group = (ViewGroup) view;
-
- int count = group.getChildCount();
- for (int i = 0; i < count; i++) {
- destroyResources(group.getChildAt(i));
- }
- }
- }
-
- static void startTrimMemory(int level) {
- if (sEgl == null || sEglConfig == null) return;
-
- GLRendererEglContext managedContext =
- (GLRendererEglContext) sEglContextStorage.get();
- // We do not have OpenGL objects
- if (managedContext == null) {
- return;
- } else {
- usePbufferSurface(managedContext.getContext());
- }
-
- if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
- GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_FULL);
- } else if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
- GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_MODERATE);
- }
- }
-
- static void endTrimMemory() {
- if (sEgl != null && sEglDisplay != null) {
- sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- }
- }
-
- private static void usePbufferSurface(EGLContext eglContext) {
- synchronized (sPbufferLock) {
- // Create a temporary 1x1 pbuffer so we have a context
- // to clear our OpenGL objects
- if (sPbuffer == null) {
- sPbuffer = sEgl.eglCreatePbufferSurface(sEglDisplay, sEglConfig, new int[] {
- EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE
- });
- }
- }
- sEgl.eglMakeCurrent(sEglDisplay, sPbuffer, sPbuffer, eglContext);
- }
-
- GLRenderer(boolean translucent) {
- mTranslucent = translucent;
-
- loadSystemProperties();
- }
-
- @Override
- void setOpaque(boolean opaque) {
- // Not supported
- }
-
- @Override
- boolean loadSystemProperties() {
- boolean value;
- boolean changed = false;
-
- String profiling = SystemProperties.get(PROFILE_PROPERTY);
- int graphType = search(VISUALIZERS, profiling);
- value = graphType >= 0;
-
- if (graphType != mProfileVisualizerType) {
- changed = true;
- mProfileVisualizerType = graphType;
-
- mProfileShapes = null;
- mProfilePaint = null;
-
- if (value) {
- mDebugDataProvider = new GraphDataProvider(graphType);
- } else {
- mDebugDataProvider = null;
- }
- }
-
- // If on-screen profiling is not enabled, we need to check whether
- // console profiling only is enabled
- if (!value) {
- value = Boolean.parseBoolean(profiling);
- }
-
- if (value != mProfileEnabled) {
- changed = true;
- mProfileEnabled = value;
-
- if (mProfileEnabled) {
- Log.d(LOG_TAG, "Profiling hardware renderer");
-
- int maxProfileFrames = SystemProperties.getInt(PROFILE_MAXFRAMES_PROPERTY,
- PROFILE_MAX_FRAMES);
- mProfileData = new float[maxProfileFrames * PROFILE_FRAME_DATA_COUNT];
- for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) {
- mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1;
- }
-
- mProfileLock = new ReentrantLock();
- } else {
- mProfileData = null;
- mProfileLock = null;
- mProfileVisualizerType = -1;
- }
-
- mProfileCurrentFrame = -PROFILE_FRAME_DATA_COUNT;
- }
-
- value = SystemProperties.getBoolean(DEBUG_DIRTY_REGIONS_PROPERTY, false);
- if (value != mDebugDirtyRegions) {
- changed = true;
- mDebugDirtyRegions = value;
-
- if (mDebugDirtyRegions) {
- Log.d(LOG_TAG, "Debugging dirty regions");
- }
- }
-
- String overdraw = SystemProperties.get(HardwareRenderer.DEBUG_OVERDRAW_PROPERTY);
- int debugOverdraw = search(OVERDRAW, overdraw);
- if (debugOverdraw != mDebugOverdraw) {
- changed = true;
- mDebugOverdraw = debugOverdraw;
- }
-
- if (loadProperties()) {
- changed = true;
- }
-
- return changed;
- }
-
- private static int search(String[] values, String value) {
- for (int i = 0; i < values.length; i++) {
- if (values[i].equals(value)) return i;
- }
- return -1;
- }
-
- @Override
- void dumpGfxInfo(PrintWriter pw, FileDescriptor fd) {
- if (mProfileEnabled) {
- pw.printf("\n\tDraw\tProcess\tExecute\n");
-
- mProfileLock.lock();
- try {
- for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) {
- if (mProfileData[i] < 0) {
- break;
- }
- pw.printf("\t%3.2f\t%3.2f\t%3.2f\n", mProfileData[i], mProfileData[i + 1],
- mProfileData[i + 2]);
- mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1;
- }
- mProfileCurrentFrame = mProfileData.length;
- } finally {
- mProfileLock.unlock();
- }
- }
- }
-
- /**
- * Indicates whether this renderer instance can track and update dirty regions.
- */
- boolean hasDirtyRegions() {
- return mDirtyRegionsEnabled;
- }
-
- /**
- * Checks for OpenGL errors. If an error has occured, {@link #destroy(boolean)}
- * is invoked and the requested flag is turned off. The error code is
- * also logged as a warning.
- */
- void checkEglErrors() {
- if (isEnabled()) {
- checkEglErrorsForced();
- }
- }
-
- private void checkEglErrorsForced() {
- int error = sEgl.eglGetError();
- if (error != EGL_SUCCESS) {
- // something bad has happened revert to
- // normal rendering.
- Log.w(LOG_TAG, "EGL error: " + GLUtils.getEGLErrorString(error));
- fallback(error != EGL11.EGL_CONTEXT_LOST);
- }
- }
-
- private void fallback(boolean fallback) {
- destroy(true);
- if (fallback) {
- // we'll try again if it was context lost
- setRequested(false);
- Log.w(LOG_TAG, "Mountain View, we've had a problem here. "
- + "Switching back to software rendering.");
- }
- }
-
- @Override
- boolean initialize(Surface surface) throws OutOfResourcesException {
- if (isRequested() && !isEnabled()) {
- boolean contextCreated = initializeEgl();
- mGl = createEglSurface(surface);
- mDestroyed = false;
-
- if (mGl != null) {
- int err = sEgl.eglGetError();
- if (err != EGL_SUCCESS) {
- destroy(true);
- setRequested(false);
- } else {
- if (mCanvas == null) {
- mCanvas = createCanvas();
- }
- setEnabled(true);
-
- if (contextCreated) {
- initAtlas();
- }
- }
-
- return mCanvas != null;
- }
- }
- return false;
- }
-
- @Override
- void updateSurface(Surface surface) throws OutOfResourcesException {
- if (isRequested() && isEnabled()) {
- createEglSurface(surface);
- }
- }
-
- @Override
- void pauseSurface(Surface surface) {
- // No-op
- }
-
- boolean initializeEgl() {
- synchronized (sEglLock) {
- if (sEgl == null && sEglConfig == null) {
- sEgl = (EGL10) EGLContext.getEGL();
-
- // Get to the default display.
- sEglDisplay = sEgl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
-
- if (sEglDisplay == EGL_NO_DISPLAY) {
- throw new RuntimeException("eglGetDisplay failed "
- + GLUtils.getEGLErrorString(sEgl.eglGetError()));
- }
-
- // We can now initialize EGL for that display
- int[] version = new int[2];
- if (!sEgl.eglInitialize(sEglDisplay, version)) {
- throw new RuntimeException("eglInitialize failed " +
- GLUtils.getEGLErrorString(sEgl.eglGetError()));
- }
-
- checkEglErrorsForced();
-
- sEglConfig = loadEglConfig();
- }
- }
-
- ManagedEGLContext managedContext = sEglContextStorage.get();
- mEglContext = managedContext != null ? managedContext.getContext() : null;
- mEglThread = Thread.currentThread();
-
- if (mEglContext == null) {
- mEglContext = createContext(sEgl, sEglDisplay, sEglConfig);
- sEglContextStorage.set(createManagedContext(mEglContext));
- return true;
- }
-
- return false;
- }
-
- private EGLConfig loadEglConfig() {
- EGLConfig eglConfig = chooseEglConfig();
- if (eglConfig == null) {
- // We tried to use EGL_SWAP_BEHAVIOR_PRESERVED_BIT, try again without
- if (sDirtyRegions) {
- sDirtyRegions = false;
- eglConfig = chooseEglConfig();
- if (eglConfig == null) {
- throw new RuntimeException("eglConfig not initialized");
- }
- } else {
- throw new RuntimeException("eglConfig not initialized");
- }
- }
- return eglConfig;
- }
-
- private EGLConfig chooseEglConfig() {
- EGLConfig[] configs = new EGLConfig[1];
- int[] configsCount = new int[1];
- int[] configSpec = getConfig(sDirtyRegions);
-
- // Debug
- final String debug = SystemProperties.get(PRINT_CONFIG_PROPERTY, "");
- if ("all".equalsIgnoreCase(debug)) {
- sEgl.eglChooseConfig(sEglDisplay, configSpec, null, 0, configsCount);
-
- EGLConfig[] debugConfigs = new EGLConfig[configsCount[0]];
- sEgl.eglChooseConfig(sEglDisplay, configSpec, debugConfigs,
- configsCount[0], configsCount);
-
- for (EGLConfig config : debugConfigs) {
- printConfig(config);
- }
- }
-
- if (!sEgl.eglChooseConfig(sEglDisplay, configSpec, configs, 1, configsCount)) {
- throw new IllegalArgumentException("eglChooseConfig failed " +
- GLUtils.getEGLErrorString(sEgl.eglGetError()));
- } else if (configsCount[0] > 0) {
- if ("choice".equalsIgnoreCase(debug)) {
- printConfig(configs[0]);
- }
- return configs[0];
- }
-
- return null;
- }
-
- private static void printConfig(EGLConfig config) {
- int[] value = new int[1];
-
- Log.d(LOG_TAG, "EGL configuration " + config + ":");
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_RED_SIZE, value);
- Log.d(LOG_TAG, " RED_SIZE = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_GREEN_SIZE, value);
- Log.d(LOG_TAG, " GREEN_SIZE = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_BLUE_SIZE, value);
- Log.d(LOG_TAG, " BLUE_SIZE = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_ALPHA_SIZE, value);
- Log.d(LOG_TAG, " ALPHA_SIZE = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_DEPTH_SIZE, value);
- Log.d(LOG_TAG, " DEPTH_SIZE = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_STENCIL_SIZE, value);
- Log.d(LOG_TAG, " STENCIL_SIZE = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLE_BUFFERS, value);
- Log.d(LOG_TAG, " SAMPLE_BUFFERS = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SAMPLES, value);
- Log.d(LOG_TAG, " SAMPLES = " + value[0]);
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_SURFACE_TYPE, value);
- Log.d(LOG_TAG, " SURFACE_TYPE = 0x" + Integer.toHexString(value[0]));
-
- sEgl.eglGetConfigAttrib(sEglDisplay, config, EGL_CONFIG_CAVEAT, value);
- Log.d(LOG_TAG, " CONFIG_CAVEAT = 0x" + Integer.toHexString(value[0]));
- }
-
- GL createEglSurface(Surface surface) throws OutOfResourcesException {
- // Check preconditions.
- if (sEgl == null) {
- throw new RuntimeException("egl not initialized");
- }
- if (sEglDisplay == null) {
- throw new RuntimeException("eglDisplay not initialized");
- }
- if (sEglConfig == null) {
- throw new RuntimeException("eglConfig not initialized");
- }
- if (Thread.currentThread() != mEglThread) {
- throw new IllegalStateException("HardwareRenderer cannot be used "
- + "from multiple threads");
- }
-
- // In case we need to destroy an existing surface
- destroySurface();
-
- // Create an EGL surface we can render into.
- if (!createSurface(surface)) {
- return null;
- }
-
- initCaches();
-
- return mEglContext.getGL();
- }
-
- private void enableDirtyRegions() {
- // If mDirtyRegions is set, this means we have an EGL configuration
- // with EGL_SWAP_BEHAVIOR_PRESERVED_BIT set
- if (sDirtyRegions) {
- if (!(mDirtyRegionsEnabled = preserveBackBuffer())) {
- Log.w(LOG_TAG, "Backbuffer cannot be preserved");
- }
- } else if (sDirtyRegionsRequested) {
- // If mDirtyRegions is not set, our EGL configuration does not
- // have EGL_SWAP_BEHAVIOR_PRESERVED_BIT; however, the default
- // swap behavior might be EGL_BUFFER_PRESERVED, which means we
- // want to set mDirtyRegions. We try to do this only if dirty
- // regions were initially requested as part of the device
- // configuration (see RENDER_DIRTY_REGIONS)
- mDirtyRegionsEnabled = isBackBufferPreserved();
- }
- }
-
- EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
- final int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, GL_VERSION, EGL_NONE };
-
- EGLContext context = egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT,
- attribs);
- if (context == null || context == EGL_NO_CONTEXT) {
- //noinspection ConstantConditions
- throw new IllegalStateException(
- "Could not create an EGL context. eglCreateContext failed with error: " +
- GLUtils.getEGLErrorString(sEgl.eglGetError()));
- }
-
- return context;
- }
-
- void destroySurface() {
- if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) {
- if (mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW))) {
- sEgl.eglMakeCurrent(sEglDisplay,
- EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- }
- sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
- mEglSurface = null;
- }
- }
-
- @Override
- void invalidate(Surface surface) {
- // Cancels any existing buffer to ensure we'll get a buffer
- // of the right size before we call eglSwapBuffers
- sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
-
- if (mEglSurface != null && mEglSurface != EGL_NO_SURFACE) {
- sEgl.eglDestroySurface(sEglDisplay, mEglSurface);
- mEglSurface = null;
- setEnabled(false);
- }
-
- if (surface.isValid()) {
- if (!createSurface(surface)) {
- return;
- }
-
- mUpdateDirtyRegions = true;
-
- if (mCanvas != null) {
- setEnabled(true);
- }
- }
- }
-
- private boolean createSurface(Surface surface) {
- mEglSurface = sEgl.eglCreateWindowSurface(sEglDisplay, sEglConfig, surface, null);
-
- if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) {
- int error = sEgl.eglGetError();
- if (error == EGL_BAD_NATIVE_WINDOW) {
- Log.e(LOG_TAG, "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
- return false;
- }
- throw new RuntimeException("createWindowSurface failed "
- + GLUtils.getEGLErrorString(error));
- }
-
- if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
- throw new IllegalStateException("eglMakeCurrent failed " +
- GLUtils.getEGLErrorString(sEgl.eglGetError()));
- }
-
- enableDirtyRegions();
-
- return true;
- }
-
- boolean validate() {
- return checkRenderContext() != SURFACE_STATE_ERROR;
- }
-
- @Override
- void setup(int width, int height, float lightX, float lightY, float lightZ, float lightRadius) {
- if (validate()) {
- mCanvas.setViewport(width, height);
- mCanvas.initializeLight(lightX, lightY, lightZ, lightRadius);
- mWidth = width;
- mHeight = height;
- }
- }
-
- @Override
- int getWidth() {
- return mWidth;
- }
-
- @Override
- int getHeight() {
- return mHeight;
- }
-
- @Override
- void setName(String name) {
- mName = name;
- }
-
- @Override
- void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
- Rect dirty) {
- if (canDraw()) {
- if (!hasDirtyRegions()) {
- dirty = null;
- }
- attachInfo.mIgnoreDirtyState = true;
- attachInfo.mDrawingTime = SystemClock.uptimeMillis();
-
- view.mPrivateFlags |= View.PFLAG_DRAWN;
-
- // We are already on the correct thread
- final int surfaceState = checkRenderContextUnsafe();
- if (surfaceState != SURFACE_STATE_ERROR) {
- HardwareCanvas canvas = mCanvas;
-
- if (mProfileEnabled) {
- mProfileLock.lock();
- }
-
- dirty = beginFrame(canvas, dirty, surfaceState);
-
- RenderNode displayList = buildDisplayList(view, canvas);
-
- flushLayerChanges();
-
- // buildDisplayList() calls into user code which can cause
- // an eglMakeCurrent to happen with a different surface/context.
- // We must therefore check again here.
- if (checkRenderContextUnsafe() == SURFACE_STATE_ERROR) {
- return;
- }
-
- int saveCount = 0;
- int status = RenderNode.STATUS_DONE;
-
- long start = getSystemTime();
- try {
- status = prepareFrame(dirty);
-
- saveCount = canvas.save();
- callbacks.onHardwarePreDraw(canvas);
-
- if (displayList != null) {
- status |= drawDisplayList(canvas, displayList, status);
- } else {
- // Shouldn't reach here
- view.draw(canvas);
- }
- } catch (Exception e) {
- Log.e(LOG_TAG, "An error has occurred while drawing:", e);
- } finally {
- callbacks.onHardwarePostDraw(canvas);
- canvas.restoreToCount(saveCount);
- view.mRecreateDisplayList = false;
-
- mDrawDelta = getSystemTime() - start;
-
- if (mDrawDelta > 0) {
- mFrameCount++;
-
- debugDirtyRegions(dirty, canvas);
- drawProfileData(attachInfo);
- }
- }
-
- onPostDraw();
-
- swapBuffers(status);
-
- if (mProfileEnabled) {
- mProfileLock.unlock();
- }
-
- attachInfo.mIgnoreDirtyState = false;
- }
- }
- }
-
- private void flushLayerChanges() {
- // Loop through and apply any pending layer changes
- for (int i = 0; i < mLayerUpdates.size(); i++) {
- HardwareLayer layer = mLayerUpdates.get(i);
- layer.flushChanges();
- if (!layer.isValid()) {
- // The layer was removed from mAttachedLayers, rewind i by 1
- // Note that this shouldn't actually happen as View.getHardwareLayer()
- // is already flushing for error checking reasons
- i--;
- } else if (layer.hasDisplayList()) {
- mCanvas.pushLayerUpdate(layer);
- }
- }
- mLayerUpdates.clear();
- }
-
- @Override
- void fence() {
- // Everything is immediate, so this is a no-op
- }
-
- private RenderNode buildDisplayList(View view, HardwareCanvas canvas) {
- view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
- == View.PFLAG_INVALIDATED;
- view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
-
- long buildDisplayListStartTime = startBuildDisplayListProfiling();
- canvas.clearLayerUpdates();
-
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
- RenderNode renderNode = view.getDisplayList();
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
-
- endBuildDisplayListProfiling(buildDisplayListStartTime);
-
- return renderNode;
- }
-
- private Rect beginFrame(HardwareCanvas canvas, Rect dirty, int surfaceState) {
- // We had to change the current surface and/or context, redraw everything
- if (surfaceState == SURFACE_STATE_UPDATED) {
- dirty = null;
- beginFrame(null);
- } else {
- int[] size = mSurfaceSize;
- beginFrame(size);
-
- if (size[1] != mHeight || size[0] != mWidth) {
- mWidth = size[0];
- mHeight = size[1];
-
- canvas.setViewport(mWidth, mHeight);
-
- dirty = null;
- }
- }
-
- if (mDebugDataProvider != null) dirty = null;
-
- return dirty;
- }
-
- private long startBuildDisplayListProfiling() {
- if (mProfileEnabled) {
- mProfileCurrentFrame += PROFILE_FRAME_DATA_COUNT;
- if (mProfileCurrentFrame >= mProfileData.length) {
- mProfileCurrentFrame = 0;
- }
-
- return System.nanoTime();
- }
- return 0;
- }
-
- private void endBuildDisplayListProfiling(long getDisplayListStartTime) {
- if (mProfileEnabled) {
- long now = System.nanoTime();
- float total = (now - getDisplayListStartTime) * 0.000001f;
- //noinspection PointlessArithmeticExpression
- mProfileData[mProfileCurrentFrame] = total;
- }
- }
-
- private int prepareFrame(Rect dirty) {
- int status;
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "prepareFrame");
- try {
- status = onPreDraw(dirty);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
- return status;
- }
-
- private int drawDisplayList(HardwareCanvas canvas, RenderNode displayList,
- int status) {
-
- long drawDisplayListStartTime = 0;
- if (mProfileEnabled) {
- drawDisplayListStartTime = System.nanoTime();
- }
-
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "drawDisplayList");
- nPrepareTree(displayList.getNativeDisplayList());
- try {
- status |= canvas.drawDisplayList(displayList, mRedrawClip,
- RenderNode.FLAG_CLIP_CHILDREN);
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
-
- if (mProfileEnabled) {
- long now = System.nanoTime();
- float total = (now - drawDisplayListStartTime) * 0.000001f;
- mProfileData[mProfileCurrentFrame + 1] = total;
- }
-
- return status;
- }
-
- private void swapBuffers(int status) {
- if ((status & RenderNode.STATUS_DREW) == RenderNode.STATUS_DREW) {
- long eglSwapBuffersStartTime = 0;
- if (mProfileEnabled) {
- eglSwapBuffersStartTime = System.nanoTime();
- }
-
- sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
-
- if (mProfileEnabled) {
- long now = System.nanoTime();
- float total = (now - eglSwapBuffersStartTime) * 0.000001f;
- mProfileData[mProfileCurrentFrame + 2] = total;
- }
-
- checkEglErrors();
- }
- }
-
- private void debugDirtyRegions(Rect dirty, HardwareCanvas canvas) {
- if (mDebugDirtyRegions) {
- if (mDebugPaint == null) {
- mDebugPaint = new Paint();
- mDebugPaint.setColor(0x7fff0000);
- }
-
- if (dirty != null && (mFrameCount & 1) == 0) {
- canvas.drawRect(dirty, mDebugPaint);
- }
- }
- }
-
- /**
- * Ensures the current EGL context and surface are the ones we expect.
- * This method throws an IllegalStateException if invoked from a thread
- * that did not initialize EGL.
- *
- * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current,
- * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or
- * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one
- *
- * @see #checkRenderContextUnsafe()
- */
- int checkRenderContext() {
- if (mEglThread != Thread.currentThread()) {
- throw new IllegalStateException("Hardware acceleration can only be used with a " +
- "single UI thread.\nOriginal thread: " + mEglThread + "\n" +
- "Current thread: " + Thread.currentThread());
- }
-
- return checkRenderContextUnsafe();
- }
-
- /**
- * Ensures the current EGL context and surface are the ones we expect.
- * This method does not check the current thread.
- *
- * @return {@link #SURFACE_STATE_ERROR} if the correct EGL context cannot be made current,
- * {@link #SURFACE_STATE_UPDATED} if the EGL context was changed or
- * {@link #SURFACE_STATE_SUCCESS} if the EGL context was the correct one
- *
- * @see #checkRenderContext()
- */
- private int checkRenderContextUnsafe() {
- if (!mEglSurface.equals(sEgl.eglGetCurrentSurface(EGL_DRAW)) ||
- !mEglContext.equals(sEgl.eglGetCurrentContext())) {
- if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
- Log.e(LOG_TAG, "eglMakeCurrent failed " +
- GLUtils.getEGLErrorString(sEgl.eglGetError()));
- fallback(true);
- return SURFACE_STATE_ERROR;
- } else {
- if (mUpdateDirtyRegions) {
- enableDirtyRegions();
- mUpdateDirtyRegions = false;
- }
- return SURFACE_STATE_UPDATED;
- }
- }
- return SURFACE_STATE_SUCCESS;
- }
-
- private static int dpToPx(int dp, float density) {
- return (int) (dp * density + 0.5f);
- }
-
- static native boolean loadProperties();
-
- static native void setupShadersDiskCache(String cacheFile);
-
- /**
- * Notifies EGL that the frame is about to be rendered.
- * @param size
- */
- static native void beginFrame(int[] size);
-
- /**
- * Returns the current system time according to the renderer.
- * This method is used for debugging only and should not be used
- * as a clock.
- */
- static native long getSystemTime();
-
- /**
- * Preserves the back buffer of the current surface after a buffer swap.
- * Calling this method sets the EGL_SWAP_BEHAVIOR attribute of the current
- * surface to EGL_BUFFER_PRESERVED. Calling this method requires an EGL
- * config that supports EGL_SWAP_BEHAVIOR_PRESERVED_BIT.
- *
- * @return True if the swap behavior was successfully changed,
- * false otherwise.
- */
- static native boolean preserveBackBuffer();
-
- /**
- * Indicates whether the current surface preserves its back buffer
- * after a buffer swap.
- *
- * @return True, if the surface's EGL_SWAP_BEHAVIOR is EGL_BUFFER_PRESERVED,
- * false otherwise
- */
- static native boolean isBackBufferPreserved();
-
- static native void nDestroyLayer(long layerPtr);
-
- private static native void nPrepareTree(long displayListPtr);
-
- class GraphDataProvider {
- /**
- * Draws the graph as bars. Frame elements are stacked on top of
- * each other.
- */
- public static final int GRAPH_TYPE_BARS = 0;
- /**
- * Draws the graph as lines. The number of series drawn corresponds
- * to the number of elements.
- */
- public static final int GRAPH_TYPE_LINES = 1;
-
- private final int mGraphType;
-
- private int mVerticalUnit;
- private int mHorizontalUnit;
- private int mHorizontalMargin;
- private int mThresholdStroke;
-
- public GraphDataProvider(int graphType) {
- mGraphType = graphType;
- }
-
- void prepare(DisplayMetrics metrics) {
- final float density = metrics.density;
-
- mVerticalUnit = dpToPx(PROFILE_DRAW_DP_PER_MS, density);
- mHorizontalUnit = dpToPx(PROFILE_DRAW_WIDTH, density);
- mHorizontalMargin = dpToPx(PROFILE_DRAW_MARGIN, density);
- mThresholdStroke = dpToPx(PROFILE_DRAW_THRESHOLD_STROKE_WIDTH, density);
- }
-
- int getGraphType() {
- return mGraphType;
- }
-
- int getVerticalUnitSize() {
- return mVerticalUnit;
- }
-
- int getHorizontalUnitSize() {
- return mHorizontalUnit;
- }
-
- int getHorizontaUnitMargin() {
- return mHorizontalMargin;
- }
-
- float[] getData() {
- return mProfileData;
- }
-
- float getThreshold() {
- return 16;
- }
-
- int getFrameCount() {
- return mProfileData.length / PROFILE_FRAME_DATA_COUNT;
- }
-
- int getElementCount() {
- return PROFILE_FRAME_DATA_COUNT;
- }
-
- int getCurrentFrame() {
- return mProfileCurrentFrame / PROFILE_FRAME_DATA_COUNT;
- }
-
- void setupGraphPaint(Paint paint, int elementIndex) {
- paint.setColor(PROFILE_DRAW_COLORS[elementIndex]);
- if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke);
- }
-
- void setupThresholdPaint(Paint paint) {
- paint.setColor(PROFILE_DRAW_THRESHOLD_COLOR);
- paint.setStrokeWidth(mThresholdStroke);
- }
-
- void setupCurrentFramePaint(Paint paint) {
- paint.setColor(PROFILE_DRAW_CURRENT_FRAME_COLOR);
- if (mGraphType == GRAPH_TYPE_LINES) paint.setStrokeWidth(mThresholdStroke);
- }
- }
-}
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index 9568760..b8e7d8c 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -110,48 +110,6 @@ public abstract class HardwareCanvas extends Canvas {
return RenderNode.STATUS_DONE;
}
- /**
- * Indicates that the specified layer must be updated as soon as possible.
- *
- * @param layer The layer to update
- *
- * @see #clearLayerUpdates()
- *
- * @hide
- */
- abstract void pushLayerUpdate(HardwareLayer layer);
-
- /**
- * Cancels a queued layer update. If the specified layer was not
- * queued for update, this method has no effect.
- *
- * @param layer The layer whose update to cancel
- *
- * @see #pushLayerUpdate(HardwareLayer)
- * @see #clearLayerUpdates()
- *
- * @hide
- */
- abstract void cancelLayerUpdate(HardwareLayer layer);
-
- /**
- * Immediately executes all enqueued layer updates.
- *
- * @see #pushLayerUpdate(HardwareLayer)
- *
- * @hide
- */
- abstract void flushLayerUpdates();
-
- /**
- * Removes all enqueued layer updates.
- *
- * @see #pushLayerUpdate(HardwareLayer)
- *
- * @hide
- */
- abstract void clearLayerUpdates();
-
public abstract void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
CanvasProperty<Float> radius, CanvasProperty<Paint> paint);
}
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index 652bcd2..6acb134 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -172,24 +172,6 @@ final class HardwareLayer {
});
}
- /**
- * This exists to minimize impact into the current HardwareLayer paths as
- * some of the specifics of how to handle error cases in the fully
- * deferred model will work
- */
- @Deprecated
- public void flushChanges() {
- if (HardwareRenderer.sUseRenderThread) {
- // Not supported, don't try.
- return;
- }
-
- boolean success = nFlushChanges(mFinalizer.get());
- if (!success) {
- destroy();
- }
- }
-
public long getLayer() {
return nGetLayer(mFinalizer.get());
}
@@ -216,33 +198,14 @@ final class HardwareLayer {
return st;
}
- /**
- * This should only be used by HardwareRenderer! Do not call directly
- */
- static HardwareLayer createTextureLayer(HardwareRenderer renderer) {
- return new HardwareLayer(renderer, nCreateTextureLayer(), LAYER_TYPE_TEXTURE);
- }
-
static HardwareLayer adoptTextureLayer(HardwareRenderer renderer, long layer) {
return new HardwareLayer(renderer, layer, LAYER_TYPE_TEXTURE);
}
- /**
- * This should only be used by HardwareRenderer! Do not call directly
- */
- static HardwareLayer createDisplayListLayer(HardwareRenderer renderer,
- int width, int height) {
- return new HardwareLayer(renderer, nCreateRenderLayer(width, height), LAYER_TYPE_DISPLAY_LIST);
- }
-
static HardwareLayer adoptDisplayListLayer(HardwareRenderer renderer, long layer) {
return new HardwareLayer(renderer, layer, LAYER_TYPE_DISPLAY_LIST);
}
- /** This also creates the underlying layer */
- private static native long nCreateTextureLayer();
- private static native long nCreateRenderLayer(int width, int height);
-
private static native void nOnTextureDestroyed(long layerUpdater);
private static native boolean nPrepare(long layerUpdater, int width, int height, boolean isOpaque);
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index d71de9f..d67c974 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -171,9 +171,6 @@ public abstract class HardwareRenderer {
*/
public static boolean sSystemRendererDisabled = false;
- /** @hide */
- public static boolean sUseRenderThread = true;
-
private boolean mEnabled;
private boolean mRequested = true;
@@ -309,7 +306,7 @@ public abstract class HardwareRenderer {
* @hide
*/
public static void setupDiskCache(File cacheDir) {
- GLRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
+ ThreadedRenderer.setupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
}
/**
@@ -469,11 +466,7 @@ public abstract class HardwareRenderer {
static HardwareRenderer create(boolean translucent) {
HardwareRenderer renderer = null;
if (GLES20Canvas.isAvailable()) {
- if (sUseRenderThread) {
- renderer = new ThreadedRenderer(translucent);
- } else {
- renderer = new GLRenderer(translucent);
- }
+ renderer = new ThreadedRenderer(translucent);
}
return renderer;
}
@@ -500,7 +493,7 @@ public abstract class HardwareRenderer {
* see {@link android.content.ComponentCallbacks}
*/
static void startTrimMemory(int level) {
- GLRenderer.startTrimMemory(level);
+ ThreadedRenderer.startTrimMemory(level);
}
/**
@@ -508,7 +501,7 @@ public abstract class HardwareRenderer {
* cleanup special resources used by the memory trimming process.
*/
static void endTrimMemory() {
- GLRenderer.endTrimMemory();
+ ThreadedRenderer.endTrimMemory();
}
/**
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/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 11db996..9b3ef7f 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -331,6 +331,14 @@ public class ThreadedRenderer extends HardwareRenderer {
}
}
+ static void startTrimMemory(int level) {
+ // TODO
+ }
+
+ static void endTrimMemory() {
+ // TODO
+ }
+
private static class AtlasInitializer {
static AtlasInitializer sInstance = new AtlasInitializer();
@@ -367,6 +375,8 @@ public class ThreadedRenderer extends HardwareRenderer {
}
}
+ static native void setupShadersDiskCache(String cacheFile);
+
private static native void nSetAtlas(GraphicBuffer buffer, long[] map);
private static native long nCreateRootRenderNode();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c7c007e..622fa8c 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
@@ -13569,12 +13593,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
- // The layer is not valid if the underlying GPU resources cannot be allocated
- mHardwareLayer.flushChanges();
- if (!mHardwareLayer.isValid()) {
- return null;
- }
-
mHardwareLayer.setLayerPaint(mLayerPaint);
RenderNode displayList = mHardwareLayer.startRecording();
updateDisplayListIfDirty(displayList, true);
@@ -19774,6 +19792,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..f3d1e3c 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;
@@ -715,17 +716,6 @@ public final class ViewRootImpl implements ViewParent,
if (!HardwareRenderer.sRendererDisabled || (HardwareRenderer.sSystemRendererDisabled
&& forceHwAccelerated)) {
- if (!HardwareRenderer.sUseRenderThread) {
- // TODO: Delete
- // Don't enable hardware acceleration when we're not on the main thread
- if (!HardwareRenderer.sSystemRendererDisabled &&
- Looper.getMainLooper() != Looper.myLooper()) {
- Log.w(HardwareRenderer.LOG_TAG, "Attempting to initialize hardware "
- + "acceleration outside of the main thread, aborting");
- return;
- }
- }
-
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.destroy(true);
}
@@ -1016,7 +1006,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 +2608,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 +3890,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 +4014,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 +5287,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 +5699,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 +5739,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 +5785,11 @@ public final class ViewRootImpl implements ViewParent,
@Override
public void onBatchedInputEventPending() {
- scheduleConsumeBatchedInput();
+ if (mUnbufferedInputDispatch) {
+ super.onBatchedInputEventPending();
+ } else {
+ scheduleConsumeBatchedInput();
+ }
}
@Override
@@ -5784,6 +5810,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);
}
/**