diff options
Diffstat (limited to 'core/java')
120 files changed, 6192 insertions, 2868 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/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index a057c3e..9160452 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -394,8 +394,18 @@ public class ActivityOptions { if (sharedElements != null) { for (int i = 0; i < sharedElements.length; i++) { Pair<View, String> sharedElement = sharedElements[i]; - names.add(sharedElement.second); - mappedNames.add(sharedElement.first.getViewName()); + String sharedElementName = sharedElement.second; + if (sharedElementName == null) { + throw new IllegalArgumentException("Shared element name must not be null"); + } + String viewName = sharedElement.first.getViewName(); + if (viewName == null) { + throw new IllegalArgumentException("Shared elements must have non-null " + + "viewNames"); + } + + names.add(sharedElementName); + mappedNames.add(viewName); } } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index d9adba3..ea46044 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -98,6 +98,7 @@ import com.android.internal.os.RuntimeInit; import com.android.internal.os.SamplingProfilerIntegration; import com.android.internal.util.FastPrintWriter; import com.android.org.conscrypt.OpenSSLSocketImpl; +import com.android.org.conscrypt.TrustedCertificateStore; import com.google.android.collect.Lists; import dalvik.system.VMRuntime; @@ -5049,6 +5050,10 @@ public final class ActivityThread { Security.addProvider(new AndroidKeyStoreProvider()); + // Make sure TrustedCertificateStore looks in the right place for CA certificates + final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId()); + TrustedCertificateStore.setDefaultUserDirectory(configDir); + Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index 703df51..0cccedc 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -129,9 +129,6 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { protected static final String KEY_SCALE_TYPE = "shared_element:scaleType"; protected static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix"; - // The background fade in/out duration. TODO: Enable tuning this. - public static final int FADE_BACKGROUND_DURATION_MS = 300; - protected static final ImageView.ScaleType[] SCALE_TYPE_VALUES = ImageView.ScaleType.values(); /** @@ -261,13 +258,8 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { if (view == null) { mEpicenterCallback.setEpicenter(null); } else { - int[] loc = new int[2]; - view.getLocationOnScreen(loc); - int left = loc[0] + Math.round(view.getTranslationX()); - int top = loc[1] + Math.round(view.getTranslationY()); - int right = left + view.getWidth(); - int bottom = top + view.getHeight(); - Rect epicenter = new Rect(left, top, right, bottom); + Rect epicenter = new Rect(); + view.getBoundsOnScreen(epicenter); mEpicenterCallback.setEpicenter(epicenter); } } @@ -352,6 +344,10 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { String name = mAllSharedElementNames.get(i); View sharedElement = sharedElements.get(name); if (sharedElement != null) { + if (sharedElement.getViewName() == null) { + throw new IllegalArgumentException("Shared elements must have " + + "non-null viewNames"); + } mSharedElementNames.add(name); mSharedElements.add(sharedElement); } @@ -504,15 +500,19 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { protected Bundle captureSharedElementState() { Bundle bundle = new Bundle(); - int[] tempLoc = new int[2]; + Rect tempBounds = new Rect(); for (int i = 0; i < mSharedElementNames.size(); i++) { View sharedElement = mSharedElements.get(i); String name = mSharedElementNames.get(i); - captureSharedElementState(sharedElement, name, bundle, tempLoc); + captureSharedElementState(sharedElement, name, bundle, tempBounds); } return bundle; } + protected long getFadeDuration() { + return getWindow().getTransitionBackgroundFadeDuration(); + } + /** * Captures placement information for Views with a shared element name for * Activity Transitions. @@ -521,20 +521,19 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { * @param name The shared element name in the target Activity to apply the placement * information for. * @param transitionArgs Bundle to store shared element placement information. - * @param tempLoc A temporary int[2] for capturing the current location of views. + * @param tempBounds A temporary Rect for capturing the current location of views. */ private static void captureSharedElementState(View view, String name, Bundle transitionArgs, - int[] tempLoc) { + Rect tempBounds) { Bundle sharedElementBundle = new Bundle(); - view.getLocationOnScreen(tempLoc); - float scaleX = view.getScaleX(); - sharedElementBundle.putInt(KEY_SCREEN_X, tempLoc[0]); - int width = Math.round(view.getWidth() * scaleX); + tempBounds.set(0, 0, view.getWidth(), view.getHeight()); + view.getBoundsOnScreen(tempBounds); + sharedElementBundle.putInt(KEY_SCREEN_X, tempBounds.left); + int width = tempBounds.width(); sharedElementBundle.putInt(KEY_WIDTH, width); - float scaleY = view.getScaleY(); - sharedElementBundle.putInt(KEY_SCREEN_Y, tempLoc[1]); - int height = Math.round(view.getHeight() * scaleY); + sharedElementBundle.putInt(KEY_SCREEN_Y, tempBounds.top); + int height = tempBounds.height(); sharedElementBundle.putInt(KEY_HEIGHT, height); sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ()); diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index 097c64e..94ea2c5 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -53,6 +53,7 @@ public class ActivityView extends ViewGroup { private int mHeight; private Surface mSurface; private int mLastVisibility; + private ActivityViewCallback mActivityViewCallback; // Only one IIntentSender or Intent may be queued at a time. Most recent one wins. IIntentSender mQueuedPendingIntent; @@ -254,6 +255,25 @@ public class ActivityView extends ViewGroup { } } + /** + * Set the callback to use to report certain state changes. + * @param callback The callback to report events to. + * + * @see ActivityViewCallback + */ + public void setCallback(ActivityViewCallback callback) { + mActivityViewCallback = callback; + } + + public static abstract class ActivityViewCallback { + /** + * Called when all activities in the ActivityView have completed and been removed. Register + * using {@link ActivityView#setCallback(ActivityViewCallback)}. Each ActivityView may + * have at most one callback registered. + */ + public abstract void onAllActivitiesComplete(ActivityView view); + } + private class ActivityViewSurfaceTextureListener implements SurfaceTextureListener { @Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, @@ -313,14 +333,32 @@ public class ActivityView extends ViewGroup { if (DEBUG) Log.v(TAG, "setVisible(): container=" + container + " visible=" + visible + " ActivityView=" + mActivityViewWeakReference.get()); } + + @Override + public void onAllActivitiesComplete(IBinder container) { + final ActivityView activityView = mActivityViewWeakReference.get(); + if (activityView != null) { + final ActivityViewCallback callback = activityView.mActivityViewCallback; + if (callback != null) { + activityView.post(new Runnable() { + @Override + public void run() { + callback.onAllActivitiesComplete(activityView); + } + }); + } + } + } } private static class ActivityContainerWrapper { private final IActivityContainer mIActivityContainer; private final CloseGuard mGuard = CloseGuard.get(); + boolean mOpened; // Protected by mGuard. ActivityContainerWrapper(IActivityContainer container) { mIActivityContainer = container; + mOpened = true; mGuard.open("release"); } @@ -388,11 +426,16 @@ public class ActivityView extends ViewGroup { } void release() { - if (DEBUG) Log.v(TAG, "ActivityContainerWrapper: release called"); - try { - mIActivityContainer.release(); - mGuard.close(); - } catch (RemoteException e) { + synchronized (mGuard) { + if (mOpened) { + if (DEBUG) Log.v(TAG, "ActivityContainerWrapper: release called"); + try { + mIActivityContainer.release(); + mGuard.close(); + } catch (RemoteException e) { + } + mOpened = false; + } } } 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 c5190d3..52d4585 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; @@ -654,6 +656,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); @@ -1747,7 +1756,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/EnterTransitionCoordinator.java b/core/java/android/app/EnterTransitionCoordinator.java index 779e3de..f54cb87 100644 --- a/core/java/android/app/EnterTransitionCoordinator.java +++ b/core/java/android/app/EnterTransitionCoordinator.java @@ -66,15 +66,16 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { Bundle resultReceiverBundle = new Bundle(); resultReceiverBundle.putParcelable(KEY_REMOTE_RECEIVER, this); mResultReceiver.send(MSG_SET_REMOTE_RECEIVER, resultReceiverBundle); - getDecor().getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - if (mIsReadyForTransition) { - getDecor().getViewTreeObserver().removeOnPreDrawListener(this); - } - return mIsReadyForTransition; - } - }); + getDecor().getViewTreeObserver().addOnPreDrawListener( + new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + if (mIsReadyForTransition) { + getDecor().getViewTreeObserver().removeOnPreDrawListener(this); + } + return mIsReadyForTransition; + } + }); } public void viewsReady(ArrayList<String> accepted, ArrayList<String> localNames) { @@ -315,7 +316,7 @@ class EnterTransitionCoordinator extends ActivityTransitionCoordinator { if (background != null) { background = background.mutate(); mBackgroundAnimator = ObjectAnimator.ofInt(background, "alpha", 255); - mBackgroundAnimator.setDuration(FADE_BACKGROUND_DURATION_MS); + mBackgroundAnimator.setDuration(getFadeDuration()); mBackgroundAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { diff --git a/core/java/android/app/ExitTransitionCoordinator.java b/core/java/android/app/ExitTransitionCoordinator.java index ba1638f..8d5b831 100644 --- a/core/java/android/app/ExitTransitionCoordinator.java +++ b/core/java/android/app/ExitTransitionCoordinator.java @@ -199,7 +199,7 @@ class ExitTransitionCoordinator extends ActivityTransitionCoordinator { } } }); - mBackgroundAnimator.setDuration(FADE_BACKGROUND_DURATION_MS); + mBackgroundAnimator.setDuration(getFadeDuration()); mBackgroundAnimator.start(); } } diff --git a/core/java/android/app/IActivityContainerCallback.aidl b/core/java/android/app/IActivityContainerCallback.aidl index 7f6d2c3..99d0a6f 100644 --- a/core/java/android/app/IActivityContainerCallback.aidl +++ b/core/java/android/app/IActivityContainerCallback.aidl @@ -21,4 +21,5 @@ import android.os.IBinder; /** @hide */ interface IActivityContainerCallback { oneway void setVisible(IBinder container, boolean visible); + oneway void onAllActivitiesComplete(IBinder container); } 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 157b8ec..04be028 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -25,6 +25,9 @@ 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.media.AudioService; +import android.net.ProxyInfo; import android.os.Bundle; import android.os.Handler; import android.os.Process; @@ -34,6 +37,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.service.trust.TrustAgentService; import android.util.Log; import com.android.org.conscrypt.TrustedCertificateStore; @@ -81,6 +85,20 @@ public class DevicePolicyManager { } /** + * Activity action: Used to indicate that the receiving activity is being started as part of the + * managed profile provisioning flow. This intent is typically sent to a mobile device + * management application (mdm) after the first part of the provisioning process is complete in + * the expectation that this app will (after optionally showing it's own UI) ultimately call + * {@link #ACTION_PROVISION_MANAGED_PROFILE} to complete the creation of the managed profile. + * + * <p> The intent may contain the extras {@link #EXTRA_PROVISIONING_TOKEN} and + * {@link #EXTRA_PROVISIONING_EMAIL_ADDRESS}. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SEND_PROVISIONING_VALUES + = "android.app.action.ACTION_SEND_PROVISIONING_VALUES"; + + /** * Activity action: Starts the provisioning flow which sets up a managed profile. * * <p>A managed profile allows data separation for example for the usage of a @@ -110,6 +128,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.ACTION_PROVISIONING_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}. @@ -118,6 +147,18 @@ public class DevicePolicyManager { = "android.app.extra.deviceAdminPackageName"; /** + * An int extra used to identify that during the current setup process the user has already + * consented to setting up a managed profile. This is typically received by + * a mobile device management application when it is started with + * {@link #ACTION_SEND_PROVISIONING_VALUES} and passed on in an intent + * {@link #ACTION_PROVISION_MANAGED_PROFILE} which starts the setup of the managed profile. The + * token indicates that steps asking for user consent can be skipped as the user has previously + * consented. + */ + 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} @@ -126,6 +167,17 @@ public class DevicePolicyManager { = "android.app.extra.defaultManagedProfileName"; /** + * A String extra holding the email address of the profile that is created during managed + * profile provisioning. This is typically received by a mobile management application when it + * is started with {@link #ACTION_SEND_PROVISIONING_VALUES} and passed on in an intent + * {@link #ACTION_PROVISION_MANAGED_PROFILE} which starts the setup of the managed profile. It + * is eventually passed on in an intent + * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE}. + */ + public static final String EXTRA_PROVISIONING_EMAIL_ADDRESS + = "android.app.extra.ManagedProfileEmailAddress"; + + /** * Activity action: ask the user to add a new device administrator to the system. * The desired policy is the ComponentName of the policy in the * {@link #EXTRA_DEVICE_ADMIN} extra field. This will invoke a UI to @@ -183,15 +235,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 @@ -1236,6 +1289,32 @@ public class DevicePolicyManager { } /** + * Set a network-independent global HTTP proxy. This is not normally what you want + * for typical HTTP proxies - they are generally network dependent. However if you're + * doing something unusual like general internal filtering this may be useful. On + * a private network where the proxy is not accessible, you may break HTTP using this. + * + * <p>This method requires the caller to be the device owner. + * + * <p>This proxy is only a recommendation and it is possible that some apps will ignore it. + * @see ProxyInfo + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated + * with. + * @param proxyInfo The a {@link ProxyInfo} object defining the new global + * HTTP proxy. A {@code null} value will clear the global HTTP proxy. + */ + public void setRecommendedGlobalProxy(ComponentName admin, ProxyInfo proxyInfo) { + if (mService != null) { + try { + mService.setRecommendedGlobalProxy(admin, proxyInfo); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** * Returns the component name setting the global proxy. * @return ComponentName object of the device admin that set the global proxy, or * null if no admin has set the proxy. @@ -1317,7 +1396,7 @@ public class DevicePolicyManager { public static final int KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS = 1 << 3; /** - * Ignore trust agent state on secure keyguard screens + * Ignore {@link TrustAgentService} state on secure keyguard screens * (e.g. PIN/Pattern/Password). */ public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 1 << 4; @@ -1754,6 +1833,23 @@ public class DevicePolicyManager { return isDeviceOwnerApp(packageName); } + /** + * Clears the current device owner. The caller must be the device owner. + * + * This function should be used cautiously as once it is called it cannot + * be undone. The device owner can only be set as a part of device setup + * before setup completes. + */ + public void clearDeviceOwnerApp() { + if (mService != null) { + try { + mService.clearDeviceOwner(mContext.getPackageName()); + } catch (RemoteException re) { + Log.w(TAG, "Failed to clear device owner"); + } + } + } + /** @hide */ public String getDeviceOwner() { if (mService != null) { @@ -1961,17 +2057,18 @@ 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 + * @param flags {@link DevicePolicyManager#FLAG_MANAGED_CAN_ACCESS_PARENT} and + * {@link DevicePolicyManager#FLAG_PARENT_CAN_ACCESS_MANAGED} are supported. */ - 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); } @@ -1979,14 +2076,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); } @@ -2161,45 +2258,6 @@ public class DevicePolicyManager { } /** - * Called by profile or device owner to re-enable a system app that was disabled by default - * when the managed profile was created. This should only be called from a profile or device - * owner running within a managed profile. - * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @param packageName The package to be re-enabled in the current profile. - */ - public void enableSystemApp(ComponentName admin, String packageName) { - if (mService != null) { - try { - mService.enableSystemApp(admin, packageName); - } catch (RemoteException e) { - Log.w(TAG, "Failed to install package: " + packageName); - } - } - } - - /** - * Called by profile or device owner to re-enable system apps by intent that were disabled - * by default when the managed profile was created. This should only be called from a profile - * or device owner running within a managed profile. - * - * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @param intent An intent matching the app(s) to be installed. All apps that resolve for this - * intent will be re-enabled in the current profile. - * @return int The number of activities that matched the intent and were installed. - */ - public int enableSystemApp(ComponentName admin, Intent intent) { - if (mService != null) { - try { - return mService.enableSystemAppWithIntent(admin, intent); - } catch (RemoteException e) { - Log.w(TAG, "Failed to install packages matching filter: " + intent); - } - } - return 0; - } - - /** * Called by a profile owner to disable account management for a specific type of account. * * <p>The calling device admin must be a profile owner. If it is not, a @@ -2330,4 +2388,56 @@ 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"); + } + } + } + + /** + * Called by profile or device owners to set the master volume mute on or off. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param on {@code true} to mute master volume, {@code false} to turn mute off. + */ + public void setMasterVolumeMuted(ComponentName admin, boolean on) { + if (mService != null) { + try { + mService.setMasterVolumeMuted(admin, on); + } catch (RemoteException re) { + Log.w(TAG, "Failed to setMasterMute on device policy service"); + } + } + } + + /** + * Called by profile or device owners to check whether the master volume mute is on or off. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @return {@code true} if master volume is muted, {@code false} if it's not. + */ + public boolean isMasterVolumeMuted(ComponentName admin) { + if (mService != null) { + try { + return mService.isMasterVolumeMuted(admin); + } catch (RemoteException re) { + Log.w(TAG, "Failed to get isMasterMute on device policy service"); + } + } + return false; + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 3c08c14..f8df780 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -20,6 +20,7 @@ package android.app.admin; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; +import android.net.ProxyInfo; import android.os.Bundle; import android.os.RemoteCallback; import android.os.UserHandle; @@ -78,6 +79,7 @@ interface IDevicePolicyManager { ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList, int userHandle); ComponentName getGlobalProxyAdmin(int userHandle); + void setRecommendedGlobalProxy(in ComponentName admin, in ProxyInfo proxyInfo); int setStorageEncryption(in ComponentName who, boolean encrypt, int userHandle); boolean getStorageEncryption(in ComponentName who, int userHandle); @@ -106,6 +108,7 @@ interface IDevicePolicyManager { boolean isDeviceOwner(String packageName); String getDeviceOwner(); String getDeviceOwnerName(); + void clearDeviceOwner(String packageName); boolean setProfileOwner(String packageName, String ownerName, int userHandle); String getProfileOwner(int userHandle); @@ -121,9 +124,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); @@ -132,9 +138,6 @@ interface IDevicePolicyManager { UserHandle createUser(in ComponentName who, in String name); boolean removeUser(in ComponentName who, in UserHandle userHandle); - void enableSystemApp(in ComponentName admin, in String packageName); - int enableSystemAppWithIntent(in ComponentName admin, in Intent intent); - void setAccountManagementDisabled(in ComponentName who, in String accountType, in boolean disabled); String[] getAccountTypesWithManagementDisabled(); @@ -144,4 +147,7 @@ interface IDevicePolicyManager { void setGlobalSetting(in ComponentName who, in String setting, in String value); void setSecureSetting(in ComponentName who, in String setting, in String value); + + void setMasterVolumeMuted(in ComponentName admin, boolean on); + boolean isMasterVolumeMuted(in ComponentName admin); } 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/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 7f8d0ab..64d80a0 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -874,6 +874,26 @@ public final class BluetoothDevice implements Parcelable { } /** + * Returns whether there is an open connection to this device. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH}. + * + * @return True if there is at least one open connection to this device. + * @hide + */ + public boolean isConnected() { + if (sService == null) { + // BT is not enabled, we cannot be connected. + return false; + } + try { + return sService.isConnected(this); + } catch (RemoteException e) { + Log.e(TAG, "", e); + return false; + } + } + + /** * Get the Bluetooth class of the remote device. * <p>Requires {@link android.Manifest.permission#BLUETOOTH}. * 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/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl index 07db8cc..a45c6b8 100644 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -58,6 +58,7 @@ interface IBluetooth boolean cancelBondProcess(in BluetoothDevice device); boolean removeBond(in BluetoothDevice device); int getBondState(in BluetoothDevice device); + boolean isConnected(in BluetoothDevice device); String getRemoteName(in BluetoothDevice device); int getRemoteType(in BluetoothDevice device); diff --git a/core/java/android/bluetooth/IBluetoothGatt.aidl b/core/java/android/bluetooth/IBluetoothGatt.aidl index 00a0750..273d76d 100644 --- a/core/java/android/bluetooth/IBluetoothGatt.aidl +++ b/core/java/android/bluetooth/IBluetoothGatt.aidl @@ -35,8 +35,6 @@ interface IBluetoothGatt { void startScan(in int appIf, in boolean isServer); void startScanWithUuids(in int appIf, in boolean isServer, in ParcelUuid[] ids); - void startScanWithUuidsScanParam(in int appIf, in boolean isServer, - in ParcelUuid[] ids, int scanWindow, int scanInterval); void startScanWithFilters(in int appIf, in boolean isServer, in ScanSettings settings, in List<ScanFilter> filters); void stopScan(in int appIf, in boolean isServer); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index cdcfd2e..ccf8451 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -2460,7 +2460,6 @@ public abstract class Context { * * @see #getSystemService * @see android.app.FingerprintManager - * @hide */ public static final String FINGERPRINT_SERVICE = "fingerprint"; @@ -2686,6 +2685,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/com/android/internal/backup/BackupConstants.java b/core/java/android/content/IRestrictionsManager.aidl index 4c276b7..b1c0a3a 100644 --- a/core/java/com/android/internal/backup/BackupConstants.java +++ b/core/java/android/content/IRestrictionsManager.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Android Open Source Project + * 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. @@ -14,15 +14,17 @@ * limitations under the License. */ -package com.android.internal.backup; +package android.content; + +import android.os.Bundle; /** - * Constants used internally between the backup manager and its transports + * Interface used by the RestrictionsManager + * @hide */ -public class BackupConstants { - 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 AGENT_ERROR = 3; - public static final int AGENT_UNKNOWN = 4; +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> + * <restrictions> + * <restriction + * android:key="<key>" + * android:restrictionType="boolean|string|integer|multi-select|null" + * ... /> + * <restriction ... /> + * </restrictions> + * </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 0ba7180..550c1f1 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -520,7 +520,7 @@ public abstract class PackageManager { * Installation return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if * the package being installed contains native code, but none that is - * compatible with the the device's CPU_ABI. + * compatible with the device's CPU_ABI. * @hide */ @SystemApi @@ -1601,7 +1601,7 @@ public abstract class PackageManager { * <p> * Throws {@link NameNotFoundException} if a package with the given name * cannot be found on the system. - * + * * @param packageName The name of the package to inspect. * @return Returns either a fully-qualified Intent that can be used to launch * the main Leanback activity in the package, or null if the package @@ -1615,7 +1615,7 @@ public abstract class PackageManager { * <p> * Throws {@link NameNotFoundException} if a package with the given name * cannot be found on the system. - * + * * @param packageName The full name (i.e. com.google.apps.contacts) of the * desired package. * @return Returns an int array of the assigned gids, or null if there are @@ -3454,7 +3454,7 @@ public abstract class PackageManager { /** - * Return the the enabled setting for a package component (activity, + * Return the enabled setting for a package component (activity, * receiver, service, provider). This returns the last value set by * {@link #setComponentEnabledSetting(ComponentName, int, int)}; in most * cases this value will be {@link #COMPONENT_ENABLED_STATE_DEFAULT} since @@ -3492,14 +3492,14 @@ public abstract class PackageManager { int newState, int flags); /** - * Return the the enabled setting for an application. This returns + * Return the enabled setting for an application. This returns * the last value set by * {@link #setApplicationEnabledSetting(String, int, int)}; in most * cases this value will be {@link #COMPONENT_ENABLED_STATE_DEFAULT} since * the value originally specified in the manifest has not been modified. * - * @param packageName The component to retrieve. - * @return Returns the current enabled state for the component. May + * @param packageName The package name of the application to retrieve. + * @return Returns the current enabled state for the application. May * be one of {@link #COMPONENT_ENABLED_STATE_ENABLED}, * {@link #COMPONENT_ENABLED_STATE_DISABLED}, or * {@link #COMPONENT_ENABLED_STATE_DEFAULT}. The last one means the @@ -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 8965faa..4cac7fd 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)) { @@ -2551,13 +2550,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, @@ -2910,7 +2909,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()); @@ -3184,13 +3183,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 c593e9e..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() { } @@ -625,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 a69a813..0901562 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 { @@ -244,7 +243,11 @@ public final class CameraManager { USE_CALLING_UID, holder); cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder()); } catch (CameraRuntimeException e) { - if (e.getReason() == CameraAccessException.CAMERA_IN_USE || + if (e.getReason() == CameraAccessException.CAMERA_DEPRECATED_HAL) { + // Use legacy camera implementation for HAL1 devices + Log.i(TAG, "Using legacy camera HAL."); + cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id); + } else if (e.getReason() == CameraAccessException.CAMERA_IN_USE || e.getReason() == CameraAccessException.MAX_CAMERAS_IN_USE || e.getReason() == CameraAccessException.CAMERA_DISABLED || e.getReason() == CameraAccessException.CAMERA_DISCONNECTED || @@ -441,6 +444,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/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java new file mode 100644 index 0000000..2d7af85 --- /dev/null +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -0,0 +1,310 @@ +/* + * 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.hardware.soundtrigger; + +import android.content.Context; +import android.content.Intent; +import android.os.Handler; + +import java.util.ArrayList; +import java.util.UUID; + +/** + * The SoundTrigger class provides access via JNI to the native service managing + * the sound trigger HAL. + * + * @hide + */ +public class SoundTrigger { + + public static final int STATUS_OK = 0; + public static final int STATUS_ERROR = Integer.MIN_VALUE; + public static final int STATUS_PERMISSION_DENIED = -1; + public static final int STATUS_NO_INIT = -19; + public static final int STATUS_BAD_VALUE = -22; + public static final int STATUS_DEAD_OBJECT = -32; + public static final int STATUS_INVALID_OPERATION = -38; + + /***************************************************************************** + * A ModuleProperties describes a given sound trigger hardware module + * managed by the native sound trigger service. Each module has a unique + * ID used to target any API call to this paricular module. Module + * properties are returned by listModules() method. + ****************************************************************************/ + public static class ModuleProperties { + /** Unique module ID provided by the native service */ + public final int id; + + /** human readable voice detection engine implementor */ + public final String implementor; + + /** human readable voice detection engine description */ + public final String description; + + /** Unique voice engine Id (changes with each version) */ + public final UUID uuid; + + /** Voice detection engine version */ + public final int version; + + /** Maximum number of active sound models */ + public final int maxSoundModels; + + /** Maximum number of key phrases */ + public final int maxKeyPhrases; + + /** Maximum number of users per key phrase */ + public final int maxUsers; + + /** Supported recognition modes (bit field, RECOGNITION_MODE_VOICE_TRIGGER ...) */ + public final int recognitionModes; + + /** Supports seamless transition to capture mode after recognition */ + public final boolean supportsCaptureTransition; + + /** Maximum buffering capacity in ms if supportsCaptureTransition() is true */ + public final int maxBufferMs; + + /** Supports capture by other use cases while detection is active */ + public final boolean supportsConcurrentCapture; + + /** Rated power consumption when detection is active with TDB silence/sound/speech ratio */ + public final int powerConsumptionMw; + + ModuleProperties(int id, String implementor, String description, + String uuid, int version, int maxSoundModels, int maxKeyPhrases, + int maxUsers, int recognitionModes, boolean supportsCaptureTransition, + int maxBufferMs, boolean supportsConcurrentCapture, + int powerConsumptionMw) { + this.id = id; + this.implementor = implementor; + this.description = description; + this.uuid = UUID.fromString(uuid); + this.version = version; + this.maxSoundModels = maxSoundModels; + this.maxKeyPhrases = maxKeyPhrases; + this.maxUsers = maxUsers; + this.recognitionModes = recognitionModes; + this.supportsCaptureTransition = supportsCaptureTransition; + this.maxBufferMs = maxBufferMs; + this.supportsConcurrentCapture = supportsConcurrentCapture; + this.powerConsumptionMw = powerConsumptionMw; + } + } + + /***************************************************************************** + * A SoundModel describes the attributes and contains the binary data used by the hardware + * implementation to detect a particular sound pattern. + * A specialized version {@link KeyPhraseSoundModel} is defined for key phrase + * sound models. + ****************************************************************************/ + public static class SoundModel { + /** Undefined sound model type */ + public static final int TYPE_UNKNOWN = -1; + + /** Keyphrase sound model */ + public static final int TYPE_KEYPHRASE = 0; + + /** Sound model type (e.g. TYPE_KEYPHRASE); */ + public final int type; + + /** Opaque data. For use by vendor implementation and enrollment application */ + public final byte[] data; + + public SoundModel(int type, byte[] data) { + this.type = type; + this.data = data; + } + } + + /***************************************************************************** + * A KeyPhrase describes a key phrase that can be detected by a + * {@link KeyPhraseSoundModel} + ****************************************************************************/ + public static class KeyPhrase { + /** Recognition modes supported for this key phrase in the model */ + public final int recognitionModes; + + /** Locale of the keyphrase. JAVA Locale string e.g en_US */ + public final String locale; + + /** Key phrase text */ + public final String text; + + /** Number of users this key phrase has been trained for */ + public final int numUsers; + + public KeyPhrase(int recognitionModes, String locale, String text, int numUsers) { + this.recognitionModes = recognitionModes; + this.locale = locale; + this.text = text; + this.numUsers = numUsers; + } + } + + /***************************************************************************** + * A KeyPhraseSoundModel is a specialized {@link SoundModel} for key phrases. + * It contains data needed by the hardware to detect a certain number of key phrases + * and the list of corresponding {@link KeyPhrase} descriptors. + ****************************************************************************/ + public static class KeyPhraseSoundModel extends SoundModel { + /** Key phrases in this sound model */ + public final KeyPhrase[] keyPhrases; // keyword phrases in model + + public KeyPhraseSoundModel(byte[] data, KeyPhrase[] keyPhrases) { + super(TYPE_KEYPHRASE, data); + this.keyPhrases = keyPhrases; + } + } + + /** + * Modes for key phrase recognition + */ + /** Simple recognition of the key phrase */ + public static final int RECOGNITION_MODE_VOICE_TRIGGER = 0x1; + /** Trigger only if one user is identified */ + public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 0x2; + /** Trigger only if one user is authenticated */ + public static final int RECOGNITION_MODE_USER_AUTHENTICATION = 0x4; + + /** + * Status codes for {@link RecognitionEvent} + */ + /** Recognition success */ + public static final int RECOGNITION_STATUS_SUCCESS = 0; + /** Recognition aborted (e.g. capture preempted by anotehr use case */ + public static final int RECOGNITION_STATUS_ABORT = 1; + /** Recognition failure */ + public static final int RECOGNITION_STATUS_FAILURE = 2; + + /** + * A RecognitionEvent is provided by the + * {@link StatusListener#onRecognition(RecognitionEvent)} + * callback upon recognition success or failure. + */ + public static class RecognitionEvent { + /** Recognition status e.g {@link #RECOGNITION_STATUS_SUCCESS} */ + public final int status; + /** Sound Model corresponding to this event callback */ + public final int soundModelHandle; + /** True if it is possible to capture audio from this utterance buffered by the hardware */ + public final boolean captureAvailable; + /** Audio session ID to be used when capturing the utterance with an AudioRecord + * if captureAvailable() is true. */ + public final int captureSession; + /** Delay in ms between end of model detection and start of audio available for capture. + * A negative value is possible (e.g. if keyphrase is also available for capture) */ + public final int captureDelayMs; + /** Opaque data for use by system applications who know about voice engine internals, + * typically during enrollment. */ + public final byte[] data; + + RecognitionEvent(int status, int soundModelHandle, boolean captureAvailable, + int captureSession, int captureDelayMs, byte[] data) { + this.status = status; + this.soundModelHandle = soundModelHandle; + this.captureAvailable = captureAvailable; + this.captureSession = captureSession; + this.captureDelayMs = captureDelayMs; + this.data = data; + } + } + + /** + * Additional data conveyed by a {@link KeyPhraseRecognitionEvent} + * for a key phrase detection. + */ + public static class KeyPhraseRecognitionExtra { + /** Confidence level for each user defined in the key phrase in the same order as + * users in the key phrase. The confidence level is expressed in percentage (0% -100%) */ + public final int[] confidenceLevels; + + /** Recognition modes matched for this event */ + public final int recognitionModes; + + KeyPhraseRecognitionExtra(int[] confidenceLevels, int recognitionModes) { + this.confidenceLevels = confidenceLevels; + this.recognitionModes = recognitionModes; + } + } + + /** + * Specialized {@link RecognitionEvent} for a key phrase detection. + */ + public static class KeyPhraseRecognitionEvent extends RecognitionEvent { + /** Indicates if the key phrase is present in the buffered audio available for capture */ + public final KeyPhraseRecognitionExtra[] keyPhraseExtras; + + /** Additional data available for each recognized key phrases in the model */ + public final boolean keyPhraseInCapture; + + KeyPhraseRecognitionEvent(int status, int soundModelHandle, boolean captureAvailable, + int captureSession, int captureDelayMs, byte[] data, + boolean keyPhraseInCapture, KeyPhraseRecognitionExtra[] keyPhraseExtras) { + super(status, soundModelHandle, captureAvailable, captureSession, captureDelayMs, data); + this.keyPhraseInCapture = keyPhraseInCapture; + this.keyPhraseExtras = keyPhraseExtras; + } + } + + /** + * Returns a list of descriptors for all harware modules loaded. + * @param modules A ModuleProperties array where the list will be returned. + * @return - {@link #STATUS_OK} in case of success + * - {@link #STATUS_ERROR} in case of unspecified error + * - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission + * - {@link #STATUS_NO_INIT} if the native service cannot be reached + * - {@link #STATUS_BAD_VALUE} if modules is null + * - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails + */ + public static native int listModules(ArrayList <ModuleProperties> modules); + + /** + * Get an interface on a hardware module to control sound models and recognition on + * this module. + * @param moduleId Sound module system identifier {@link ModuleProperties#id}. mandatory. + * @param listener {@link StatusListener} interface. Mandatory. + * @param handler the Handler that will receive the callabcks. Can be null if default handler + * is OK. + * @return a valid sound module in case of success or null in case of error. + */ + public static SoundTriggerModule attachModule(int moduleId, + StatusListener listener, + Handler handler) { + if (listener == null) { + return null; + } + SoundTriggerModule module = new SoundTriggerModule(moduleId, listener, handler); + return module; + } + + /** + * Interface provided by the client application when attaching to a {@link SoundTriggerModule} + * to received recognition and error notifications. + */ + public static interface StatusListener { + /** + * Called when recognition succeeds of fails + */ + public abstract void onRecognition(RecognitionEvent event); + + /** + * Called when the sound trigger native service dies + */ + public abstract void onServiceDied(); + } +} diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java new file mode 100644 index 0000000..776f85d --- /dev/null +++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java @@ -0,0 +1,192 @@ +/* + * 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.hardware.soundtrigger; + +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import java.lang.ref.WeakReference; +import java.util.UUID; + +/** + * The SoundTriggerModule provides APIs to control sound models and sound detection + * on a given sound trigger hardware module. + * + * @hide + */ +public class SoundTriggerModule { + private long mNativeContext; + + private int mId; + private NativeEventHandlerDelegate mEventHandlerDelegate; + + private static final int EVENT_RECOGNITION = 1; + private static final int EVENT_SERVICE_DIED = 2; + + SoundTriggerModule(int moduleId, SoundTrigger.StatusListener listener, Handler handler) { + mId = moduleId; + mEventHandlerDelegate = new NativeEventHandlerDelegate(listener, handler); + native_setup(new WeakReference<SoundTriggerModule>(this)); + } + private native void native_setup(Object module_this); + + @Override + protected void finalize() { + native_finalize(); + } + private native void native_finalize(); + + /** + * Detach from this module. The {@link SoundTrigger.StatusListener} callback will not be called + * anymore and associated resources will be released. + * */ + public native void detach(); + + /** + * Load a {@link SoundTrigger.SoundModel} to the hardware. A sound model must be loaded in + * order to start listening to a key phrase in this model. + * @param model The sound model to load. + * @param soundModelHandle an array of int where the sound model handle will be returned. + * @return - {@link SoundTrigger#STATUS_OK} in case of success + * - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error + * - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have + * system permission + * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached + * - {@link SoundTrigger#STATUS_BAD_VALUE} if parameters are invalid + * - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native + * service fails + * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence + */ + public native int loadSoundModel(SoundTrigger.SoundModel model, int[] soundModelHandle); + + /** + * Unload a {@link SoundTrigger.SoundModel} and abort any pendiong recognition + * @param soundModelHandle The sound model handle + * @return - {@link SoundTrigger#STATUS_OK} in case of success + * - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error + * - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have + * system permission + * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached + * - {@link SoundTrigger#STATUS_BAD_VALUE} if the sound model handle is invalid + * - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native + * service fails + */ + public native int unloadSoundModel(int soundModelHandle); + + /** + * Start listening to all key phrases in a {@link SoundTrigger.SoundModel}. + * Recognition must be restarted after each callback (success or failure) received on + * the {@link SoundTrigger.StatusListener}. + * @param soundModelHandle The sound model handle to start listening to + * @param data Opaque data for use by the implementation for this recognition + * @return - {@link SoundTrigger#STATUS_OK} in case of success + * - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error + * - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have + * system permission + * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached + * - {@link SoundTrigger#STATUS_BAD_VALUE} if the sound model handle is invalid + * - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native + * service fails + * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence + */ + public native int startRecognition(int soundModelHandle, byte[] data); + + /** + * Stop listening to all key phrases in a {@link SoundTrigger.SoundModel} + * @param soundModelHandle The sound model handle to stop listening to + * @return - {@link SoundTrigger#STATUS_OK} in case of success + * - {@link SoundTrigger#STATUS_ERROR} in case of unspecified error + * - {@link SoundTrigger#STATUS_PERMISSION_DENIED} if the caller does not have + * system permission + * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached + * - {@link SoundTrigger#STATUS_BAD_VALUE} if the sound model handle is invalid + * - {@link SoundTrigger#STATUS_DEAD_OBJECT} if the binder transaction to the native + * service fails + * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence + */ + public native int stopRecognition(int soundModelHandle); + + private class NativeEventHandlerDelegate { + private final Handler mHandler; + + NativeEventHandlerDelegate(final SoundTrigger.StatusListener listener, + Handler handler) { + // find the looper for our new event handler + Looper looper; + if (handler != null) { + looper = handler.getLooper(); + } else { + looper = Looper.myLooper(); + if (looper == null) { + looper = Looper.getMainLooper(); + } + } + + // construct the event handler with this looper + if (looper != null) { + // implement the event handler delegate + mHandler = new Handler(looper) { + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case EVENT_RECOGNITION: + if (listener != null) { + listener.onRecognition( + (SoundTrigger.RecognitionEvent)msg.obj); + } + break; + case EVENT_SERVICE_DIED: + if (listener != null) { + listener.onServiceDied(); + } + break; + default: + break; + } + } + }; + } else { + mHandler = null; + } + } + + Handler handler() { + return mHandler; + } + } + + @SuppressWarnings("unused") + private static void postEventFromNative(Object module_ref, + int what, int arg1, int arg2, Object obj) { + SoundTriggerModule module = (SoundTriggerModule)((WeakReference)module_ref).get(); + if (module == null) { + return; + } + + NativeEventHandlerDelegate delegate = module.mEventHandlerDelegate; + if (delegate != null) { + Handler handler = delegate.handler(); + if (handler != null) { + Message m = handler.obtainMessage(what, arg1, arg2, obj); + handler.sendMessage(m); + } + } + } +} + diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 60e76e0..27402668 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -893,6 +893,7 @@ public class ConnectivityManager { case NetworkCapabilities.NET_CAPABILITY_IMS: case NetworkCapabilities.NET_CAPABILITY_RCS: case NetworkCapabilities.NET_CAPABILITY_XCAP: + case NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED: //there by default continue; default: // At least one capability usually provided by unrestricted diff --git a/core/java/android/net/IpPrefix.aidl b/core/java/android/net/IpPrefix.aidl new file mode 100644 index 0000000..9e552c7 --- /dev/null +++ b/core/java/android/net/IpPrefix.aidl @@ -0,0 +1,20 @@ +/** + * + * 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; + +parcelable IpPrefix; diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java new file mode 100644 index 0000000..dfe0384 --- /dev/null +++ b/core/java/android/net/IpPrefix.java @@ -0,0 +1,173 @@ +/* + * 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.os.Parcel; +import android.os.Parcelable; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; + +/** + * This class represents an IP prefix, i.e., a contiguous block of IP addresses aligned on a + * power of two boundary (also known as an "IP subnet"). A prefix is specified by two pieces of + * information: + * + * <ul> + * <li>A starting IP address (IPv4 or IPv6). This is the first IP address of the prefix. + * <li>A prefix length. This specifies the length of the prefix by specifing the number of bits + * in the IP address, starting from the most significant bit in network byte order, that + * are constant for all addresses in the prefix. + * </ul> + * + * For example, the prefix <code>192.0.2.0/24</code> covers the 256 IPv4 addresses from + * <code>192.0.2.0</code> to <code>192.0.2.255</code>, inclusive, and the prefix + * <code>2001:db8:1:2</code> covers the 2^64 IPv6 addresses from <code>2001:db8:1:2::</code> to + * <code>2001:db8:1:2:ffff:ffff:ffff:ffff</code>, inclusive. + * + * Objects of this class are immutable. + */ +public class IpPrefix implements Parcelable { + private final byte[] address; // network byte order + private final int prefixLength; + + /** + * Constructs a new {@code IpPrefix} from a byte array containing an IPv4 or IPv6 address in + * network byte order and a prefix length. + * + * @param address the IP address. Must be non-null and exactly 4 or 16 bytes long. + * @param prefixLength the prefix length. Must be >= 0 and <= (32 or 128) (IPv4 or IPv6). + * + * @hide + */ + public IpPrefix(byte[] address, int prefixLength) { + if (address.length != 4 && address.length != 16) { + throw new IllegalArgumentException( + "IpPrefix has " + address.length + " bytes which is neither 4 nor 16"); + } + if (prefixLength < 0 || prefixLength > (address.length * 8)) { + throw new IllegalArgumentException("IpPrefix with " + address.length + + " bytes has invalid prefix length " + prefixLength); + } + this.address = address.clone(); + this.prefixLength = prefixLength; + // TODO: Validate that the non-prefix bits are zero + } + + /** + * @hide + */ + public IpPrefix(InetAddress address, int prefixLength) { + this(address.getAddress(), prefixLength); + } + + /** + * Compares this {@code IpPrefix} object against the specified object in {@code obj}. Two + * objects are equal if they have the same startAddress and prefixLength. + * + * @param obj the object to be tested for equality. + * @return {@code true} if both objects are equal, {@code false} otherwise. + */ + @Override + public boolean equals(Object obj) { + if (!(obj instanceof IpPrefix)) { + return false; + } + IpPrefix that = (IpPrefix) obj; + return Arrays.equals(this.address, that.address) && this.prefixLength == that.prefixLength; + } + + /** + * Gets the hashcode of the represented IP prefix. + * + * @return the appropriate hashcode value. + */ + @Override + public int hashCode() { + return Arrays.hashCode(address) + 11 * prefixLength; + } + + /** + * Returns a copy of the first IP address in the prefix. Modifying the returned object does not + * change this object's contents. + * + * @return the address in the form of a byte array. + */ + public InetAddress getAddress() { + try { + return InetAddress.getByAddress(address); + } catch (UnknownHostException e) { + // Cannot happen. InetAddress.getByAddress can only throw an exception if the byte + // array is the wrong length, but we check that in the constructor. + return null; + } + } + + /** + * Returns a copy of the IP address bytes in network order (the highest order byte is the zeroth + * element). Modifying the returned array does not change this object's contents. + * + * @return the address in the form of a byte array. + */ + public byte[] getRawAddress() { + return address.clone(); + } + + /** + * Returns the prefix length of this {@code IpAddress}. + * + * @return the prefix length. + */ + public int getPrefixLength() { + return prefixLength; + } + + /** + * Implement the Parcelable interface. + * @hide + */ + public int describeContents() { + return 0; + } + + /** + * Implement the Parcelable interface. + * @hide + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeByteArray(address); + dest.writeInt(prefixLength); + } + + /** + * Implement the Parcelable interface. + * @hide + */ + public static final Creator<IpPrefix> CREATOR = + new Creator<IpPrefix>() { + public IpPrefix createFromParcel(Parcel in) { + byte[] address = in.createByteArray(); + int prefixLength = in.readInt(); + return new IpPrefix(address, prefixLength); + } + + public IpPrefix[] newArray(int size) { + return new IpPrefix[size]; + } + }; +} diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java index d07c0b61..5246078 100644 --- a/core/java/android/net/LinkAddress.java +++ b/core/java/android/net/LinkAddress.java @@ -39,18 +39,13 @@ import static android.system.OsConstants.RT_SCOPE_UNIVERSE; * <ul> * <li>An IP address and prefix length (e.g., {@code 2001:db8::1/64} or {@code 192.0.2.1/24}). * The address must be unicast, as multicast addresses cannot be assigned to interfaces. - * <li>Address flags: A bitmask of {@code IFA_F_*} values representing properties - * of the address. - * <li>Address scope: An integer defining the scope in which the address is unique (e.g., - * {@code RT_SCOPE_LINK} or {@code RT_SCOPE_SITE}). - * <ul> - *<p> - * When constructing a {@code LinkAddress}, the IP address and prefix are required. The flags and - * scope are optional. If they are not specified, the flags are set to zero, and the scope will be - * determined based on the IP address (e.g., link-local addresses will be created with a scope of - * {@code RT_SCOPE_LINK}, global addresses with {@code RT_SCOPE_UNIVERSE}, - * etc.) If they are specified, they are not checked for validity. - * + * <li>Address flags: A bitmask of {@code OsConstants.IFA_F_*} values representing properties + * of the address (e.g., {@code android.system.OsConstants.IFA_F_OPTIMISTIC}). + * <li>Address scope: One of the {@code OsConstants.IFA_F_*} values; defines the scope in which + * the address is unique (e.g., + * {@code android.system.OsConstants.RT_SCOPE_LINK} or + * {@code android.system.OsConstants.RT_SCOPE_UNIVERSE}). + * </ul> */ public class LinkAddress implements Parcelable { /** @@ -202,7 +197,9 @@ public class LinkAddress implements Parcelable { /** * Compares this {@code LinkAddress} instance against {@code obj}. Two addresses are equal if - * their address, prefix length, flags and scope are equal. + * their address, prefix length, flags and scope are equal. Thus, for example, two addresses + * that have the same address and prefix length are not equal if one of them is deprecated and + * the other is not. * * @param obj the object to be tested for equality. * @return {@code true} if both objects are equal, {@code false} otherwise. @@ -236,6 +233,7 @@ public class LinkAddress implements Parcelable { * @param other the {@code LinkAddress} to compare to. * @return {@code true} if both objects have the same address and prefix length, {@code false} * otherwise. + * @hide */ public boolean isSameAddressAs(LinkAddress other) { return address.equals(other.address) && prefixLength == other.prefixLength; @@ -251,11 +249,20 @@ public class LinkAddress implements Parcelable { /** * Returns the prefix length of this {@code LinkAddress}. */ - public int getNetworkPrefixLength() { + public int getPrefixLength() { return prefixLength; } /** + * Returns the prefix length of this {@code LinkAddress}. + * TODO: Delete all callers and remove in favour of getPrefixLength(). + * @hide + */ + public int getNetworkPrefixLength() { + return getPrefixLength(); + } + + /** * Returns the flags of this {@code LinkAddress}. */ public int getFlags() { diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index cff9025..bb05936 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -77,9 +77,15 @@ public class LinkProperties implements Parcelable { } } + /** + * @hide + */ public LinkProperties() { } + /** + * @hide + */ public LinkProperties(LinkProperties source) { if (source != null) { mIfaceName = source.getInterfaceName(); @@ -267,7 +273,7 @@ public class LinkProperties implements Parcelable { } /** - * Returns all the {@link LinkAddress} for DNS servers on this link. + * Returns all the {@link InetAddress} for DNS servers on this link. * * @return An umodifiable {@link List} of {@link InetAddress} for DNS servers on * this link. @@ -477,12 +483,12 @@ public class LinkProperties implements Parcelable { String domainName = "Domains: " + mDomains; - String mtu = "MTU: " + mMtu; + String mtu = " MTU: " + mMtu; String routes = " Routes: ["; for (RouteInfo route : mRoutes) routes += route.toString() + ","; routes += "] "; - String proxy = (mHttpProxy == null ? "" : "HttpProxy: " + mHttpProxy.toString() + " "); + String proxy = (mHttpProxy == null ? "" : " HttpProxy: " + mHttpProxy.toString() + " "); String stacked = ""; if (mStackedLinks.values().size() > 0) { diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index 7e8b1f1..3d0874b 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -80,6 +80,11 @@ public abstract class NetworkAgent extends Handler { */ public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3; + /* centralize place where base network score, and network score scaling, will be + * stored, so as we can consistently compare apple and oranges, or wifi, ethernet and LTE + */ + public static final int WIFI_BASE_SCORE = 60; + /** * Sent by the NetworkAgent to ConnectivityService to pass the current * network score. diff --git a/core/java/android/net/ProxyDataTracker.java b/core/java/android/net/ProxyDataTracker.java index 573a8f8..a578383 100644 --- a/core/java/android/net/ProxyDataTracker.java +++ b/core/java/android/net/ProxyDataTracker.java @@ -48,6 +48,7 @@ public class ProxyDataTracker extends BaseNetworkStateTracker { // TODO: investigate how to get these DNS addresses from the system. private static final String DNS1 = "8.8.8.8"; private static final String DNS2 = "8.8.4.4"; + private static final String INTERFACE_NAME = "ifb0"; private static final String REASON_ENABLED = "enabled"; private static final String REASON_DISABLED = "disabled"; private static final String REASON_PROXY_DOWN = "proxy_down"; @@ -107,10 +108,11 @@ public class ProxyDataTracker extends BaseNetworkStateTracker { mNetworkCapabilities = new NetworkCapabilities(); mNetworkInfo.setIsAvailable(true); try { - mLinkProperties.addDnsServer(InetAddress.getByName(DNS1)); - mLinkProperties.addDnsServer(InetAddress.getByName(DNS2)); + mLinkProperties.addDnsServer(InetAddress.getByName(DNS1)); + mLinkProperties.addDnsServer(InetAddress.getByName(DNS2)); + mLinkProperties.setInterfaceName(INTERFACE_NAME); } catch (UnknownHostException e) { - Log.e(TAG, "Could not add DNS address", e); + Log.e(TAG, "Could not add DNS address", e); } } diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java index ad8e4f7..8b42bcd 100644 --- a/core/java/android/net/RouteInfo.java +++ b/core/java/android/net/RouteInfo.java @@ -35,10 +35,10 @@ import java.util.Objects; * * A route contains three pieces of information: * <ul> - * <li>a destination {@link LinkAddress} for directly-connected subnets. If this is - * {@code null} it indicates a default route of the address family (IPv4 or IPv6) + * <li>a destination {@link IpPrefix} specifying the network destinations covered by this route. + * If this is {@code null} it indicates a default route of the address family (IPv4 or IPv6) * implied by the gateway IP address. - * <li>a gateway {@link InetAddress} for default routes. If this is {@code null} it + * <li>a gateway {@link InetAddress} indicating the next hop to use. If this is {@code null} it * indicates a directly-connected route. * <li>an interface (which may be unspecified). * </ul> @@ -49,6 +49,7 @@ import java.util.Objects; public class RouteInfo implements Parcelable { /** * The IP destination address for this route. + * TODO: Make this an IpPrefix. */ private final LinkAddress mDestination; @@ -80,6 +81,19 @@ public class RouteInfo implements Parcelable { * @param destination the destination prefix * @param gateway the IP address to route packets through * @param iface the interface name to send packets on + * + * TODO: Convert to use IpPrefix. + * + * @hide + */ + public RouteInfo(IpPrefix destination, InetAddress gateway, String iface) { + this(destination == null ? null : + new LinkAddress(destination.getAddress(), destination.getPrefixLength()), + gateway, iface); + } + + /** + * @hide */ public RouteInfo(LinkAddress destination, InetAddress gateway, String iface) { if (destination == null) { @@ -105,7 +119,7 @@ public class RouteInfo implements Parcelable { mHasGateway = (!gateway.isAnyLocalAddress()); mDestination = new LinkAddress(NetworkUtils.getNetworkPart(destination.getAddress(), - destination.getNetworkPrefixLength()), destination.getNetworkPrefixLength()); + destination.getPrefixLength()), destination.getPrefixLength()); if ((destination.getAddress() instanceof Inet4Address && (gateway instanceof Inet4Address == false)) || (destination.getAddress() instanceof Inet6Address && @@ -128,8 +142,17 @@ public class RouteInfo implements Parcelable { * <p> * Destination and gateway may not both be null. * - * @param destination the destination address and prefix in a {@link LinkAddress} + * @param destination the destination address and prefix in an {@link IpPrefix} * @param gateway the {@link InetAddress} to route packets through + * + * @hide + */ + public RouteInfo(IpPrefix destination, InetAddress gateway) { + this(destination, gateway, null); + } + + /** + * @hide */ public RouteInfo(LinkAddress destination, InetAddress gateway) { this(destination, gateway, null); @@ -139,16 +162,27 @@ public class RouteInfo implements Parcelable { * Constructs a default {@code RouteInfo} object. * * @param gateway the {@link InetAddress} to route packets through + * + * @hide */ public RouteInfo(InetAddress gateway) { - this(null, gateway, null); + this((LinkAddress) null, gateway, null); } /** * Constructs a {@code RouteInfo} object representing a direct connected subnet. * - * @param destination the {@link LinkAddress} describing the address and prefix + * @param destination the {@link IpPrefix} describing the address and prefix * length of the subnet. + * + * @hide + */ + public RouteInfo(IpPrefix destination) { + this(destination, null, null); + } + + /** + * @hide */ public RouteInfo(LinkAddress destination) { this(destination, null, null); @@ -176,29 +210,37 @@ public class RouteInfo implements Parcelable { private boolean isHost() { return (mDestination.getAddress() instanceof Inet4Address && - mDestination.getNetworkPrefixLength() == 32) || + mDestination.getPrefixLength() == 32) || (mDestination.getAddress() instanceof Inet6Address && - mDestination.getNetworkPrefixLength() == 128); + mDestination.getPrefixLength() == 128); } private boolean isDefault() { boolean val = false; if (mGateway != null) { if (mGateway instanceof Inet4Address) { - val = (mDestination == null || mDestination.getNetworkPrefixLength() == 0); + val = (mDestination == null || mDestination.getPrefixLength() == 0); } else { - val = (mDestination == null || mDestination.getNetworkPrefixLength() == 0); + val = (mDestination == null || mDestination.getPrefixLength() == 0); } } return val; } /** - * Retrieves the destination address and prefix length in the form of a {@link LinkAddress}. + * Retrieves the destination address and prefix length in the form of an {@link IpPrefix}. * - * @return {@link LinkAddress} specifying the destination. This is never {@code null}. + * @return {@link IpPrefix} specifying the destination. This is never {@code null}. + */ + public IpPrefix getDestination() { + return new IpPrefix(mDestination.getAddress(), mDestination.getPrefixLength()); + } + + /** + * TODO: Convert callers to use IpPrefix and then remove. + * @hide */ - public LinkAddress getDestination() { + public LinkAddress getDestinationLinkAddress() { return mDestination; } @@ -233,7 +275,8 @@ public class RouteInfo implements Parcelable { /** * Indicates if this route is a host route (ie, matches only a single host address). * - * @return {@code true} if the destination has a prefix length of 32/128 for v4/v6. + * @return {@code true} if the destination has a prefix length of 32 or 128 for IPv4 or IPv6, + * respectively. * @hide */ public boolean isHostRoute() { @@ -263,7 +306,7 @@ public class RouteInfo implements Parcelable { // match the route destination and destination with prefix length InetAddress dstNet = NetworkUtils.getNetworkPart(destination, - mDestination.getNetworkPrefixLength()); + mDestination.getPrefixLength()); return mDestination.getAddress().equals(dstNet); } @@ -285,8 +328,8 @@ public class RouteInfo implements Parcelable { for (RouteInfo route : routes) { if (NetworkUtils.addressTypeMatches(route.mDestination.getAddress(), dest)) { if ((bestRoute != null) && - (bestRoute.mDestination.getNetworkPrefixLength() >= - route.mDestination.getNetworkPrefixLength())) { + (bestRoute.mDestination.getPrefixLength() >= + route.mDestination.getPrefixLength())) { continue; } if (route.matches(dest)) bestRoute = route; @@ -295,13 +338,22 @@ public class RouteInfo implements Parcelable { return bestRoute; } + /** + * Returns a human-readable description of this object. + */ public String toString() { String val = ""; if (mDestination != null) val = mDestination.toString(); - if (mGateway != null) val += " -> " + mGateway.getHostAddress(); + val += " ->"; + if (mGateway != null) val += " " + mGateway.getHostAddress(); + if (mInterface != null) val += " " + mInterface; return val; } + /** + * Compares this RouteInfo object against the specified object and indicates if they are equal. + * @return {@code true} if the objects are equal, {@code false} otherwise. + */ public boolean equals(Object obj) { if (this == obj) return true; @@ -309,11 +361,14 @@ public class RouteInfo implements Parcelable { RouteInfo target = (RouteInfo) obj; - return Objects.equals(mDestination, target.getDestination()) && + return Objects.equals(mDestination, target.getDestinationLinkAddress()) && Objects.equals(mGateway, target.getGateway()) && Objects.equals(mInterface, target.getInterface()); } + /** + * Returns a hashcode for this <code>RouteInfo</code> object. + */ public int hashCode() { return (mDestination == null ? 0 : mDestination.hashCode() * 41) + (mGateway == null ? 0 :mGateway.hashCode() * 47) @@ -339,7 +394,7 @@ public class RouteInfo implements Parcelable { } else { dest.writeByte((byte) 1); dest.writeByteArray(mDestination.getAddress().getAddress()); - dest.writeInt(mDestination.getNetworkPrefixLength()); + dest.writeInt(mDestination.getPrefixLength()); } if (mGateway == null) { diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index e84b695..975bfc2 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -292,6 +292,17 @@ public class Environment { } /** + * Returns the config directory for a user. This is for use by system services to store files + * relating to the user which should be readable by any app running as that user. + * + * @hide + */ + public static File getUserConfigDirectory(int userId) { + return new File(new File(new File( + getDataDirectory(), "misc"), "user"), Integer.toString(userId)); + } + + /** * Returns whether the Encrypted File System feature is enabled on the device or not. * @return <code>true</code> if Encrypted File System feature is enabled, <code>false</code> * if disabled. 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/PowerManager.java b/core/java/android/os/PowerManager.java index cd1cd30..92e80a5 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -314,6 +314,11 @@ public final class PowerManager { * The value to pass as the 'reason' argument to reboot() to * reboot into recovery mode (for applying system updates, doing * factory resets, etc.). + * <p> + * Requires the {@link android.Manifest.permission#RECOVERY} + * permission (in addition to + * {@link android.Manifest.permission#REBOOT}). + * </p> */ public static final String REBOOT_RECOVERY = "recovery"; diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 112ec1d..86c749a 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -156,6 +156,12 @@ public class Process { public static final int LAST_ISOLATED_UID = 99999; /** + * Defines the gid shared by all applications running under the same profile. + * @hide + */ + public static final int SHARED_USER_GID = 9997; + + /** * First gid for applications to share resources. Used when forward-locking * is enabled but all UserHandles need to be able to read the resources. * @hide 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/UserHandle.java b/core/java/android/os/UserHandle.java index 6e693a4..914c170 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -145,6 +145,14 @@ public final class UserHandle implements Parcelable { } /** + * Returns the gid shared between all apps with this userId. + * @hide + */ + public static final int getUserGid(int userId) { + return getUid(userId, Process.SHARED_USER_GID); + } + + /** * Returns the shared app gid for a given uid or appId. * @hide */ diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index f7a89ba..91fbb9d 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -481,10 +481,11 @@ public class UserManager { } /** - * @hide * Returns whether the current user has been disallowed from performing certain actions * or setting certain settings. - * @param restrictionKey the string key representing the restriction + * + * @param restrictionKey The string key representing the restriction. + * @return {@code true} if the current user has the given restriction, {@code false} otherwise. */ public boolean hasUserRestriction(String restrictionKey) { return hasUserRestriction(restrictionKey, Process.myUserHandle()); 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/print/ILayoutResultCallback.aidl b/core/java/android/print/ILayoutResultCallback.aidl index 43b8c30..68c1dac 100644 --- a/core/java/android/print/ILayoutResultCallback.aidl +++ b/core/java/android/print/ILayoutResultCallback.aidl @@ -16,6 +16,7 @@ package android.print; +import android.os.ICancellationSignal; import android.print.PrintDocumentInfo; /** @@ -24,6 +25,8 @@ import android.print.PrintDocumentInfo; * @hide */ oneway interface ILayoutResultCallback { + void onLayoutStarted(ICancellationSignal cancellation, int sequence); void onLayoutFinished(in PrintDocumentInfo info, boolean changed, int sequence); void onLayoutFailed(CharSequence error, int sequence); + void onLayoutCanceled(int sequence); } diff --git a/core/java/android/print/IPrintDocumentAdapter.aidl b/core/java/android/print/IPrintDocumentAdapter.aidl index 2b95c12..9d384fb 100644 --- a/core/java/android/print/IPrintDocumentAdapter.aidl +++ b/core/java/android/print/IPrintDocumentAdapter.aidl @@ -37,5 +37,4 @@ oneway interface IPrintDocumentAdapter { void write(in PageRange[] pages, in ParcelFileDescriptor fd, IWriteResultCallback callback, int sequence); void finish(); - void cancel(); } diff --git a/core/java/android/print/IWriteResultCallback.aidl b/core/java/android/print/IWriteResultCallback.aidl index 8281c4e..8fb33e1 100644 --- a/core/java/android/print/IWriteResultCallback.aidl +++ b/core/java/android/print/IWriteResultCallback.aidl @@ -16,6 +16,7 @@ package android.print; +import android.os.ICancellationSignal; import android.print.PageRange; /** @@ -24,6 +25,8 @@ import android.print.PageRange; * @hide */ oneway interface IWriteResultCallback { + void onWriteStarted(ICancellationSignal cancellation, int sequence); void onWriteFinished(in PageRange[] pages, int sequence); void onWriteFailed(CharSequence error, int sequence); + void onWriteCanceled(int sequence); } diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java index c6254e0..2810d55 100644 --- a/core/java/android/print/PrintAttributes.java +++ b/core/java/android/print/PrintAttributes.java @@ -151,6 +151,105 @@ public final class PrintAttributes implements Parcelable { mColorMode = colorMode; } + /** + * Gets whether this print attributes are in portrait orientation, + * which is the media size is in portrait and all orientation dependent + * attributes such as resolution and margins are properly adjusted. + * + * @return Whether this print attributes are in portrait. + * + * @hide + */ + public boolean isPortrait() { + return mMediaSize.isPortrait(); + } + + /** + * Gets a new print attributes instance which is in portrait orientation, + * which is the media size is in portrait and all orientation dependent + * attributes such as resolution and margins are properly adjusted. + * + * @return New instance in portrait orientation if this one is in + * landscape, otherwise this instance. + * + * @hide + */ + public PrintAttributes asPortrait() { + if (isPortrait()) { + return this; + } + + PrintAttributes attributes = new PrintAttributes(); + + // Rotate the media size. + attributes.setMediaSize(getMediaSize().asPortrait()); + + // Rotate the resolution. + Resolution oldResolution = getResolution(); + Resolution newResolution = new Resolution( + oldResolution.getId(), + oldResolution.getLabel(), + oldResolution.getVerticalDpi(), + oldResolution.getHorizontalDpi()); + attributes.setResolution(newResolution); + + // Rotate the physical margins. + Margins oldMinMargins = getMinMargins(); + Margins newMinMargins = new Margins( + oldMinMargins.getBottomMils(), + oldMinMargins.getLeftMils(), + oldMinMargins.getTopMils(), + oldMinMargins.getRightMils()); + attributes.setMinMargins(newMinMargins); + + attributes.setColorMode(getColorMode()); + + return attributes; + } + + /** + * Gets a new print attributes instance which is in landscape orientation, + * which is the media size is in landscape and all orientation dependent + * attributes such as resolution and margins are properly adjusted. + * + * @return New instance in landscape orientation if this one is in + * portrait, otherwise this instance. + * + * @hide + */ + public PrintAttributes asLandscape() { + if (!isPortrait()) { + return this; + } + + PrintAttributes attributes = new PrintAttributes(); + + // Rotate the media size. + attributes.setMediaSize(getMediaSize().asLandscape()); + + // Rotate the resolution. + Resolution oldResolution = getResolution(); + Resolution newResolution = new Resolution( + oldResolution.getId(), + oldResolution.getLabel(), + oldResolution.getVerticalDpi(), + oldResolution.getHorizontalDpi()); + attributes.setResolution(newResolution); + + // Rotate the physical margins. + Margins oldMinMargins = getMinMargins(); + Margins newMargins = new Margins( + oldMinMargins.getTopMils(), + oldMinMargins.getRightMils(), + oldMinMargins.getBottomMils(), + oldMinMargins.getLeftMils()); + attributes.setMinMargins(newMargins); + + attributes.setColorMode(getColorMode()); + + return attributes; + } + @Override public void writeToParcel(Parcel parcel, int flags) { if (mMediaSize != null) { diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java index 811751d..9361286 100644 --- a/core/java/android/print/PrintManager.java +++ b/core/java/android/print/PrintManager.java @@ -24,6 +24,7 @@ import android.content.IntentSender.SendIntentException; import android.os.Bundle; import android.os.CancellationSignal; import android.os.Handler; +import android.os.ICancellationSignal; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; @@ -41,6 +42,7 @@ import libcore.io.IoUtils; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -50,12 +52,12 @@ import java.util.Map; * <p> * To obtain a handle to the print manager do the following: * </p> - * + * * <pre> * PrintManager printManager = * (PrintManager) context.getSystemService(Context.PRINT_SERVICE); * </pre> - * + * * <h3>Print mechanics</h3> * <p> * The key idea behind printing on the platform is that the content to be printed @@ -344,7 +346,7 @@ public final class PrintManager { try { mService.cancelPrintJob(printJobId, mAppId, mUserId); } catch (RemoteException re) { - Log.e(LOG_TAG, "Error cancleing a print job: " + printJobId, re); + Log.e(LOG_TAG, "Error canceling a print job: " + printJobId, re); } } @@ -505,30 +507,17 @@ public final class PrintManager { private static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub implements ActivityLifecycleCallbacks { - private final Object mLock = new Object(); - private CancellationSignal mLayoutOrWriteCancellation; - - private Activity mActivity; // Strong reference OK - cleared in finish() - - private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish + private Activity mActivity; // Strong reference OK - cleared in destroy - private Handler mHandler; // Strong reference OK - cleared in finish() + private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in destroy - private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in finish + private Handler mHandler; // Strong reference OK - cleared in destroy - private LayoutSpec mLastLayoutSpec; + private IPrintDocumentAdapterObserver mObserver; // Strong reference OK - cleared in destroy - private WriteSpec mLastWriteSpec; - - private boolean mStartReqeusted; - private boolean mStarted; - - private boolean mFinishRequested; - private boolean mFinished; - - private boolean mDestroyed; + private DestroyableCallback mPendingCallback; public PrintDocumentAdapterDelegate(Activity activity, PrintDocumentAdapter documentAdapter) { @@ -542,11 +531,10 @@ public final class PrintManager { public void setObserver(IPrintDocumentAdapterObserver observer) { final boolean destroyed; synchronized (mLock) { - if (!mDestroyed) { - mObserver = observer; - } - destroyed = mDestroyed; + mObserver = observer; + destroyed = isDestroyedLocked(); } + if (destroyed) { try { observer.onDestroy(); @@ -559,126 +547,89 @@ public final class PrintManager { @Override public void start() { synchronized (mLock) { - // Started called or finish called or destroyed - nothing to do. - if (mStartReqeusted || mFinishRequested || mDestroyed) { - return; + // If destroyed the handler is null. + if (!isDestroyedLocked()) { + mHandler.obtainMessage(MyHandler.MSG_ON_START, + mDocumentAdapter).sendToTarget(); } - - mStartReqeusted = true; - - doPendingWorkLocked(); } } @Override public void layout(PrintAttributes oldAttributes, PrintAttributes newAttributes, ILayoutResultCallback callback, Bundle metadata, int sequence) { - final boolean destroyed; - synchronized (mLock) { - destroyed = mDestroyed; - // If start called and not finished called and not destroyed - do some work. - if (mStartReqeusted && !mFinishRequested && !mDestroyed) { - // Layout cancels write and overrides layout. - if (mLastWriteSpec != null) { - IoUtils.closeQuietly(mLastWriteSpec.fd); - mLastWriteSpec = null; - } - - mLastLayoutSpec = new LayoutSpec(); - mLastLayoutSpec.callback = callback; - mLastLayoutSpec.oldAttributes = oldAttributes; - mLastLayoutSpec.newAttributes = newAttributes; - mLastLayoutSpec.metadata = metadata; - mLastLayoutSpec.sequence = sequence; - - // Cancel the previous cancellable operation.When the - // cancellation completes we will do the pending work. - if (cancelPreviousCancellableOperationLocked()) { - return; - } - doPendingWorkLocked(); - } + ICancellationSignal cancellationTransport = CancellationSignal.createTransport(); + try { + callback.onLayoutStarted(cancellationTransport, sequence); + } catch (RemoteException re) { + // The spooler is dead - can't recover. + Log.e(LOG_TAG, "Error notifying for layout start", re); + return; } - if (destroyed) { - try { - callback.onLayoutFailed(null, sequence); - } catch (RemoteException re) { - Log.i(LOG_TAG, "Error notifying for cancelled layout", re); + + synchronized (mLock) { + // If destroyed the handler is null. + if (isDestroyedLocked()) { + return; } + + CancellationSignal cancellationSignal = CancellationSignal.fromTransport( + cancellationTransport); + + SomeArgs args = SomeArgs.obtain(); + args.arg1 = mDocumentAdapter; + args.arg2 = oldAttributes; + args.arg3 = newAttributes; + args.arg4 = cancellationSignal; + args.arg5 = new MyLayoutResultCallback(callback, sequence); + args.arg6 = metadata; + + mHandler.obtainMessage(MyHandler.MSG_ON_LAYOUT, args).sendToTarget(); } } @Override public void write(PageRange[] pages, ParcelFileDescriptor fd, IWriteResultCallback callback, int sequence) { - final boolean destroyed; - synchronized (mLock) { - destroyed = mDestroyed; - // If start called and not finished called and not destroyed - do some work. - if (mStartReqeusted && !mFinishRequested && !mDestroyed) { - // Write cancels previous writes. - if (mLastWriteSpec != null) { - IoUtils.closeQuietly(mLastWriteSpec.fd); - mLastWriteSpec = null; - } - mLastWriteSpec = new WriteSpec(); - mLastWriteSpec.callback = callback; - mLastWriteSpec.pages = pages; - mLastWriteSpec.fd = fd; - mLastWriteSpec.sequence = sequence; - - // Cancel the previous cancellable operation.When the - // cancellation completes we will do the pending work. - if (cancelPreviousCancellableOperationLocked()) { - return; - } - - doPendingWorkLocked(); - } - } - if (destroyed) { - try { - callback.onWriteFailed(null, sequence); - } catch (RemoteException re) { - Log.i(LOG_TAG, "Error notifying for cancelled write", re); - } + ICancellationSignal cancellationTransport = CancellationSignal.createTransport(); + try { + callback.onWriteStarted(cancellationTransport, sequence); + } catch (RemoteException re) { + // The spooler is dead - can't recover. + Log.e(LOG_TAG, "Error notifying for write start", re); + return; } - } - @Override - public void finish() { synchronized (mLock) { - // Start not called or finish called or destroyed - nothing to do. - if (!mStartReqeusted || mFinishRequested || mDestroyed) { + // If destroyed the handler is null. + if (isDestroyedLocked()) { return; } - mFinishRequested = true; + CancellationSignal cancellationSignal = CancellationSignal.fromTransport( + cancellationTransport); - // When the current write or layout complete we - // will do the pending work. - if (mLastLayoutSpec != null || mLastWriteSpec != null) { - if (DEBUG) { - Log.i(LOG_TAG, "Waiting for current operation"); - } - return; - } + SomeArgs args = SomeArgs.obtain(); + args.arg1 = mDocumentAdapter; + args.arg2 = pages; + args.arg3 = fd; + args.arg4 = cancellationSignal; + args.arg5 = new MyWriteResultCallback(callback, fd, sequence); - doPendingWorkLocked(); + mHandler.obtainMessage(MyHandler.MSG_ON_WRITE, args).sendToTarget(); } } @Override - public void cancel() { - // Start not called or finish called or destroyed - nothing to do. - if (!mStartReqeusted || mFinishRequested || mDestroyed) { - return; - } - // Request cancellation of pending work if needed. + public void finish() { synchronized (mLock) { - cancelPreviousCancellableOperationLocked(); + // If destroyed the handler is null. + if (!isDestroyedLocked()) { + mHandler.obtainMessage(MyHandler.MSG_ON_FINISH, + mDocumentAdapter).sendToTarget(); + } } } @@ -719,20 +670,14 @@ public final class PrintManager { // Note the the spooler has a death recipient that observes if // this process gets killed so we cover the case of onDestroy not // being called due to this process being killed to reclaim memory. - final IPrintDocumentAdapterObserver observer; + IPrintDocumentAdapterObserver observer = null; synchronized (mLock) { if (activity == mActivity) { - mDestroyed = true; observer = mObserver; - clearLocked(); - } else { - observer = null; - activity = null; + destroyLocked(); } } if (observer != null) { - activity.getApplication().unregisterActivityLifecycleCallbacks( - PrintDocumentAdapterDelegate.this); try { observer.onDestroy(); } catch (RemoteException re) { @@ -741,67 +686,39 @@ public final class PrintManager { } } - private boolean isFinished() { - return mDocumentAdapter == null; + private boolean isDestroyedLocked() { + return (mActivity == null); } - private void clearLocked() { + private void destroyLocked() { + mActivity.getApplication().unregisterActivityLifecycleCallbacks( + PrintDocumentAdapterDelegate.this); mActivity = null; + mDocumentAdapter = null; + + // This method is only called from the main thread, so + // clearing the messages guarantees that any time a + // message is handled we are not in a destroyed state. + mHandler.removeMessages(MyHandler.MSG_ON_START); + mHandler.removeMessages(MyHandler.MSG_ON_LAYOUT); + mHandler.removeMessages(MyHandler.MSG_ON_WRITE); + mHandler.removeMessages(MyHandler.MSG_ON_FINISH); mHandler = null; - mLayoutOrWriteCancellation = null; - mLastLayoutSpec = null; - if (mLastWriteSpec != null) { - IoUtils.closeQuietly(mLastWriteSpec.fd); - mLastWriteSpec = null; - } - } - private boolean cancelPreviousCancellableOperationLocked() { - if (mLayoutOrWriteCancellation != null) { - mLayoutOrWriteCancellation.cancel(); - if (DEBUG) { - Log.i(LOG_TAG, "Cancelling previous operation"); - } - return true; - } - return false; - } + mObserver = null; - private void doPendingWorkLocked() { - if (mStartReqeusted && !mStarted) { - mStarted = true; - mHandler.sendEmptyMessage(MyHandler.MSG_START); - } else if (mLastLayoutSpec != null) { - mHandler.sendEmptyMessage(MyHandler.MSG_LAYOUT); - } else if (mLastWriteSpec != null) { - mHandler.sendEmptyMessage(MyHandler.MSG_WRITE); - } else if (mFinishRequested && !mFinished) { - mFinished = true; - mHandler.sendEmptyMessage(MyHandler.MSG_FINISH); + if (mPendingCallback != null) { + mPendingCallback.destroy(); + mPendingCallback = null; } } - private class LayoutSpec { - ILayoutResultCallback callback; - PrintAttributes oldAttributes; - PrintAttributes newAttributes; - Bundle metadata; - int sequence; - } - - private class WriteSpec { - IWriteResultCallback callback; - PageRange[] pages; - ParcelFileDescriptor fd; - int sequence; - } - private final class MyHandler extends Handler { - public static final int MSG_START = 1; - public static final int MSG_LAYOUT = 2; - public static final int MSG_WRITE = 3; - public static final int MSG_FINISH = 4; + public static final int MSG_ON_START = 1; + public static final int MSG_ON_LAYOUT = 2; + public static final int MSG_ON_WRITE = 3; + public static final int MSG_ON_FINISH = 4; public MyHandler(Looper looper) { super(looper, null, true); @@ -809,84 +726,71 @@ public final class PrintManager { @Override public void handleMessage(Message message) { - if (isFinished()) { - return; - } switch (message.what) { - case MSG_START: { - final PrintDocumentAdapter adapter; - synchronized (mLock) { - adapter = mDocumentAdapter; - } - if (adapter != null) { - adapter.onStart(); + case MSG_ON_START: { + if (DEBUG) { + Log.i(LOG_TAG, "onStart()"); } + + ((PrintDocumentAdapter) message.obj).onStart(); } break; - case MSG_LAYOUT: { - final PrintDocumentAdapter adapter; - final CancellationSignal cancellation; - final LayoutSpec layoutSpec; + case MSG_ON_LAYOUT: { + SomeArgs args = (SomeArgs) message.obj; + PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1; + PrintAttributes oldAttributes = (PrintAttributes) args.arg2; + PrintAttributes newAttributes = (PrintAttributes) args.arg3; + CancellationSignal cancellation = (CancellationSignal) args.arg4; + LayoutResultCallback callback = (LayoutResultCallback) args.arg5; + Bundle metadata = (Bundle) args.arg6; + args.recycle(); - synchronized (mLock) { - adapter = mDocumentAdapter; - layoutSpec = mLastLayoutSpec; - mLastLayoutSpec = null; - cancellation = new CancellationSignal(); - mLayoutOrWriteCancellation = cancellation; + if (DEBUG) { + StringBuilder builder = new StringBuilder(); + builder.append("PrintDocumentAdapter#onLayout() {\n"); + builder.append("\n oldAttributes:").append(oldAttributes); + builder.append("\n newAttributes:").append(newAttributes); + builder.append("\n preview:").append(metadata.getBoolean( + PrintDocumentAdapter.EXTRA_PRINT_PREVIEW)); + builder.append("\n}"); + Log.i(LOG_TAG, builder.toString()); } - if (layoutSpec != null && adapter != null) { - if (DEBUG) { - Log.i(LOG_TAG, "Performing layout"); - } - adapter.onLayout(layoutSpec.oldAttributes, - layoutSpec.newAttributes, cancellation, - new MyLayoutResultCallback(layoutSpec.callback, - layoutSpec.sequence), layoutSpec.metadata); - } + adapter.onLayout(oldAttributes, newAttributes, cancellation, + callback, metadata); } break; - case MSG_WRITE: { - final PrintDocumentAdapter adapter; - final CancellationSignal cancellation; - final WriteSpec writeSpec; + case MSG_ON_WRITE: { + SomeArgs args = (SomeArgs) message.obj; + PrintDocumentAdapter adapter = (PrintDocumentAdapter) args.arg1; + PageRange[] pages = (PageRange[]) args.arg2; + ParcelFileDescriptor fd = (ParcelFileDescriptor) args.arg3; + CancellationSignal cancellation = (CancellationSignal) args.arg4; + WriteResultCallback callback = (WriteResultCallback) args.arg5; + args.recycle(); - synchronized (mLock) { - adapter = mDocumentAdapter; - writeSpec = mLastWriteSpec; - mLastWriteSpec = null; - cancellation = new CancellationSignal(); - mLayoutOrWriteCancellation = cancellation; + if (DEBUG) { + StringBuilder builder = new StringBuilder(); + builder.append("PrintDocumentAdapter#onWrite() {\n"); + builder.append("\n pages:").append(Arrays.toString(pages)); + builder.append("\n}"); + Log.i(LOG_TAG, builder.toString()); } - if (writeSpec != null && adapter != null) { - if (DEBUG) { - Log.i(LOG_TAG, "Performing write"); - } - adapter.onWrite(writeSpec.pages, writeSpec.fd, - cancellation, new MyWriteResultCallback(writeSpec.callback, - writeSpec.fd, writeSpec.sequence)); - } + adapter.onWrite(pages, fd, cancellation, callback); } break; - case MSG_FINISH: { + case MSG_ON_FINISH: { if (DEBUG) { - Log.i(LOG_TAG, "Performing finish"); + Log.i(LOG_TAG, "onFinish()"); } - final PrintDocumentAdapter adapter; - final Activity activity; + + ((PrintDocumentAdapter) message.obj).onFinish(); + + // Done printing, so destroy this instance as it + // should not be used anymore. synchronized (mLock) { - adapter = mDocumentAdapter; - activity = mActivity; - clearLocked(); - } - if (adapter != null) { - adapter.onFinish(); - } - if (activity != null) { - activity.getApplication().unregisterActivityLifecycleCallbacks( - PrintDocumentAdapterDelegate.this); + destroyLocked(); } } break; @@ -898,7 +802,12 @@ public final class PrintManager { } } - private final class MyLayoutResultCallback extends LayoutResultCallback { + private interface DestroyableCallback { + public void destroy(); + } + + private final class MyLayoutResultCallback extends LayoutResultCallback + implements DestroyableCallback { private ILayoutResultCallback mCallback; private final int mSequence; @@ -910,25 +819,31 @@ public final class PrintManager { @Override public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { - if (info == null) { - throw new NullPointerException("document info cannot be null"); - } final ILayoutResultCallback callback; synchronized (mLock) { - if (mDestroyed) { - Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " - + "finish the printing activity before print completion?"); - return; - } callback = mCallback; - clearLocked(); } - if (callback != null) { + + // If the callback is null we are destroyed. + if (callback == null) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion " + + "or did you invoke a callback after finish?"); + return; + } + + try { + if (info == null) { + throw new NullPointerException("document info cannot be null"); + } + try { callback.onLayoutFinished(info, changed, mSequence); } catch (RemoteException re) { Log.e(LOG_TAG, "Error calling onLayoutFinished", re); } + } finally { + destroy(); } } @@ -936,46 +851,64 @@ public final class PrintManager { public void onLayoutFailed(CharSequence error) { final ILayoutResultCallback callback; synchronized (mLock) { - if (mDestroyed) { - Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " - + "finish the printing activity before print completion?"); - return; - } callback = mCallback; - clearLocked(); } - if (callback != null) { - try { - callback.onLayoutFailed(error, mSequence); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error calling onLayoutFailed", re); - } + + // If the callback is null we are destroyed. + if (callback == null) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion " + + "or did you invoke a callback after finish?"); + return; + } + + try { + callback.onLayoutFailed(error, mSequence); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling onLayoutFailed", re); + } finally { + destroy(); } } @Override public void onLayoutCancelled() { + final ILayoutResultCallback callback; synchronized (mLock) { - if (mDestroyed) { - Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " - + "finish the printing activity before print completion?"); - return; - } - clearLocked(); + callback = mCallback; + } + + // If the callback is null we are destroyed. + if (callback == null) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion " + + "or did you invoke a callback after finish?"); + return; + } + + try { + callback.onLayoutCanceled(mSequence); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling onLayoutFailed", re); + } finally { + destroy(); } } - private void clearLocked() { - mLayoutOrWriteCancellation = null; - mCallback = null; - doPendingWorkLocked(); + @Override + public void destroy() { + synchronized (mLock) { + mCallback = null; + mPendingCallback = null; + } } } - private final class MyWriteResultCallback extends WriteResultCallback { + private final class MyWriteResultCallback extends WriteResultCallback + implements DestroyableCallback { private ParcelFileDescriptor mFd; - private int mSequence; private IWriteResultCallback mCallback; + private final int mSequence; public MyWriteResultCallback(IWriteResultCallback callback, ParcelFileDescriptor fd, int sequence) { @@ -988,26 +921,32 @@ public final class PrintManager { public void onWriteFinished(PageRange[] pages) { final IWriteResultCallback callback; synchronized (mLock) { - if (mDestroyed) { - Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " - + "finish the printing activity before print completion?"); - return; - } callback = mCallback; - clearLocked(); - } - if (pages == null) { - throw new IllegalArgumentException("pages cannot be null"); } - if (pages.length == 0) { - throw new IllegalArgumentException("pages cannot be empty"); + + // If the callback is null we are destroyed. + if (callback == null) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion " + + "or did you invoke a callback after finish?"); + return; } - if (callback != null) { + + try { + if (pages == null) { + throw new IllegalArgumentException("pages cannot be null"); + } + if (pages.length == 0) { + throw new IllegalArgumentException("pages cannot be empty"); + } + try { callback.onWriteFinished(pages, mSequence); } catch (RemoteException re) { Log.e(LOG_TAG, "Error calling onWriteFinished", re); } + } finally { + destroy(); } } @@ -1015,41 +954,58 @@ public final class PrintManager { public void onWriteFailed(CharSequence error) { final IWriteResultCallback callback; synchronized (mLock) { - if (mDestroyed) { - Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " - + "finish the printing activity before print completion?"); - return; - } callback = mCallback; - clearLocked(); } - if (callback != null) { - try { - callback.onWriteFailed(error, mSequence); - } catch (RemoteException re) { - Log.e(LOG_TAG, "Error calling onWriteFailed", re); - } + + // If the callback is null we are destroyed. + if (callback == null) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion " + + "or did you invoke a callback after finish?"); + return; + } + + try { + callback.onWriteFailed(error, mSequence); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling onWriteFailed", re); + } finally { + destroy(); } } @Override public void onWriteCancelled() { + final IWriteResultCallback callback; synchronized (mLock) { - if (mDestroyed) { - Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " - + "finish the printing activity before print completion?"); - return; - } - clearLocked(); + callback = mCallback; + } + + // If the callback is null we are destroyed. + if (callback == null) { + Log.e(LOG_TAG, "PrintDocumentAdapter is destroyed. Did you " + + "finish the printing activity before print completion " + + "or did you invoke a callback after finish?"); + return; + } + + try { + callback.onWriteCanceled(mSequence); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling onWriteCanceled", re); + } finally { + destroy(); } } - private void clearLocked() { - mLayoutOrWriteCancellation = null; - IoUtils.closeQuietly(mFd); - mCallback = null; - mFd = null; - doPendingWorkLocked(); + @Override + public void destroy() { + synchronized (mLock) { + IoUtils.closeQuietly(mFd); + mCallback = null; + mFd = null; + mPendingCallback = null; + } } } } diff --git a/core/java/android/print/PrinterDiscoverySession.java b/core/java/android/print/PrinterDiscoverySession.java index d32b71b..abb441b 100644 --- a/core/java/android/print/PrinterDiscoverySession.java +++ b/core/java/android/print/PrinterDiscoverySession.java @@ -72,9 +72,9 @@ public final class PrinterDiscoverySession { } } - public final void startPrinterDisovery(List<PrinterId> priorityList) { + public final void startPrinterDiscovery(List<PrinterId> priorityList) { if (isDestroyed()) { - Log.w(LOG_TAG, "Ignoring start printers dsicovery - session destroyed"); + Log.w(LOG_TAG, "Ignoring start printers discovery - session destroyed"); return; } if (!mIsPrinterDiscoveryStarted) { @@ -122,7 +122,7 @@ public final class PrinterDiscoverySession { try { mPrintManager.stopPrinterStateTracking(printerId, mUserId); } catch (RemoteException re) { - Log.e(LOG_TAG, "Error stoping printer state tracking", re); + Log.e(LOG_TAG, "Error stopping printer state tracking", re); } } diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java index eb0ac2e..1557ab0 100644 --- a/core/java/android/printservice/PrintService.java +++ b/core/java/android/printservice/PrintService.java @@ -201,9 +201,9 @@ public abstract class PrintService extends Service { * should build another one using the {@link PrintJobInfo.Builder} class. You * can specify any standard properties and add advanced, printer specific, * ones via {@link PrintJobInfo.Builder#putAdvancedOption(String, String) - * PrintJobInfo.Builder#putAdvancedOption(String, String)} and {@link + * PrintJobInfo.Builder.putAdvancedOption(String, String)} and {@link * PrintJobInfo.Builder#putAdvancedOption(String, int) - * PrintJobInfo.Builder#putAdvancedOption(String, int)}. The advanced options + * PrintJobInfo.Builder.putAdvancedOption(String, int)}. The advanced options * are not interpreted by the system, they will not be visible to applications, * and can only be accessed by your print service via {@link * PrintJob#getAdvancedStringOption(String) PrintJob.getAdvancedStringOption(String)} @@ -212,14 +212,26 @@ public abstract class PrintService extends Service { * <p> * If the advanced print options activity offers changes to the standard print * options, you can get the current {@link android.print.PrinterInfo} using the - * "android.intent.extra.print.EXTRA_PRINTER_INFO" extra which will allow you to - * present the user with UI options supported by the current printer. For example, - * if the current printer does not support a give media size, you should not - * offer it in the advanced print options dialog. + * {@link #EXTRA_PRINTER_INFO} extra which will allow you to present the user + * with UI options supported by the current printer. For example, if the current + * printer does not support a given media size, you should not offer it in the + * advanced print options UI. * </p> + * + * @see #EXTRA_PRINTER_INFO */ public static final String EXTRA_PRINT_JOB_INFO = "android.intent.extra.print.PRINT_JOB_INFO"; + /** + * If you declared an optional activity with advanced print options via the + * {@link R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity} + * attribute, this extra is used to pass in the currently selected printer's + * {@link android.print.PrinterInfo} to your activity allowing you to inspect it. + * + * @see #EXTRA_PRINT_JOB_INFO + */ + public static final String EXTRA_PRINTER_INFO = "android.intent.extra.print.PRINTER_INFO"; + private Handler mHandler; private IPrintServiceClient mClient; diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java index a34d9c3..3853003 100644 --- a/core/java/android/provider/Browser.java +++ b/core/java/android/provider/Browser.java @@ -25,6 +25,7 @@ import android.database.Cursor; import android.database.DatabaseUtils; import android.graphics.BitmapFactory; import android.net.Uri; +import android.os.Build; import android.provider.BrowserContract.Bookmarks; import android.provider.BrowserContract.Combined; import android.provider.BrowserContract.History; @@ -155,8 +156,8 @@ public class Browser { * @param title Title for the bookmark. Can be null or empty string. * @param url Url for the bookmark. Can be null or empty string. */ - public static final void saveBookmark(Context c, - String title, + public static final void saveBookmark(Context c, + String title, String url) { Intent i = new Intent(Intent.ACTION_INSERT, Browser.BOOKMARKS_URI); i.putExtra("title", title); @@ -233,10 +234,10 @@ public class Browser { * * @param cr The ContentResolver used to access the database. */ - public static final Cursor getAllBookmarks(ContentResolver cr) throws + public static final Cursor getAllBookmarks(ContentResolver cr) throws IllegalStateException { return cr.query(Bookmarks.CONTENT_URI, - new String[] { Bookmarks.URL }, + new String[] { Bookmarks.URL }, Bookmarks.IS_FOLDER + " = 0", null, null); } @@ -397,19 +398,17 @@ public class Browser { // TODO make a single request to the provider to do this in a single transaction Cursor cursor = null; try { - + // Select non-bookmark history, ordered by date cursor = cr.query(History.CONTENT_URI, new String[] { History._ID, History.URL, History.DATE_LAST_VISITED }, null, null, History.DATE_LAST_VISITED + " ASC"); if (cursor.moveToFirst() && cursor.getCount() >= MAX_HISTORY_COUNT) { - final WebIconDatabase iconDb = WebIconDatabase.getInstance(); /* eliminate oldest history items */ for (int i = 0; i < TRUNCATE_N_OLDEST; i++) { cr.delete(ContentUris.withAppendedId(History.CONTENT_URI, cursor.getLong(0)), - null, null); - iconDb.releaseIconForPageUrl(cursor.getString(1)); + null, null); if (!cursor.moveToNext()) break; } } @@ -469,13 +468,6 @@ public class Browser { cursor = cr.query(History.CONTENT_URI, new String[] { History.URL }, whereClause, null, null); if (cursor.moveToFirst()) { - final WebIconDatabase iconDb = WebIconDatabase.getInstance(); - do { - // Delete favicons - // TODO don't release if the URL is bookmarked - iconDb.releaseIconForPageUrl(cursor.getString(0)); - } while (cursor.moveToNext()); - cr.delete(History.CONTENT_URI, whereClause, null); } } catch (IllegalStateException e) { @@ -520,7 +512,7 @@ public class Browser { * @param cr The ContentResolver used to access the database. * @param url url to remove. */ - public static final void deleteFromHistory(ContentResolver cr, + public static final void deleteFromHistory(ContentResolver cr, String url) { cr.delete(History.CONTENT_URI, History.URL + "=?", new String[] { url }); } @@ -554,7 +546,7 @@ public class Browser { Log.e(LOGTAG, "clearSearches", e); } } - + /** * Request all icons from the database. This call must either be called * in the main thread or have had Looper.prepare() invoked in the calling @@ -563,12 +555,12 @@ public class Browser { * @param cr The ContentResolver used to access the database. * @param where Clause to be used to limit the query from the database. * Must be an allowable string to be passed into a database query. - * @param listener IconListener that gets the icons once they are + * @param listener IconListener that gets the icons once they are * retrieved. */ public static final void requestAllIcons(ContentResolver cr, String where, WebIconDatabase.IconListener listener) { - WebIconDatabase.getInstance().bulkRequestIconForPageUrl(cr, where, listener); + // Do nothing: this is no longer used. } /** diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 8c7e879..ba66e65 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -1156,8 +1156,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 { @@ -1167,7 +1165,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"; @@ -1175,8 +1190,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"; @@ -1184,8 +1197,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/MediaStore.java b/core/java/android/provider/MediaStore.java index cfab1b3..0fe764f 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -1886,6 +1886,9 @@ public final class MediaStore { * The MIME type for entries in this table. */ public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/radio"; + + // Not instantiable. + private Radio() { } } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 55c66ba..1001677 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4470,6 +4470,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/service/fingerprint/FingerprintManager.java b/core/java/android/service/fingerprint/FingerprintManager.java index dd2030b..2fcec52 100644 --- a/core/java/android/service/fingerprint/FingerprintManager.java +++ b/core/java/android/service/fingerprint/FingerprintManager.java @@ -31,7 +31,6 @@ import android.util.Log; /** * A class that coordinates access to the fingerprint hardware. - * @hide */ public class FingerprintManager { diff --git a/core/java/android/service/fingerprint/FingerprintManagerReceiver.java b/core/java/android/service/fingerprint/FingerprintManagerReceiver.java index 5960791..34f1655 100644 --- a/core/java/android/service/fingerprint/FingerprintManagerReceiver.java +++ b/core/java/android/service/fingerprint/FingerprintManagerReceiver.java @@ -13,7 +13,6 @@ package android.service.fingerprint; * 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. - * @hide */ public class FingerprintManagerReceiver { diff --git a/core/java/android/service/trust/TrustAgentService.java b/core/java/android/service/trust/TrustAgentService.java index ed835e4..a6cddae 100644 --- a/core/java/android/service/trust/TrustAgentService.java +++ b/core/java/android/service/trust/TrustAgentService.java @@ -17,7 +17,6 @@ package android.service.trust; import android.Manifest; -import android.annotation.SystemApi; import android.annotation.SdkConstant; import android.app.Service; import android.content.ComponentName; @@ -57,10 +56,7 @@ import android.util.Slog; * <pre> * <trust-agent xmlns:android="http://schemas.android.com/apk/res/android" * android:settingsActivity=".TrustAgentSettings" /></pre> - * - * @hide */ -@SystemApi public class TrustAgentService extends Service { private final String TAG = TrustAgentService.class.getSimpleName() + "[" + getClass().getSimpleName() + "]"; diff --git a/core/java/android/service/voice/DspInfo.java b/core/java/android/service/voice/DspInfo.java new file mode 100644 index 0000000..0862309 --- /dev/null +++ b/core/java/android/service/voice/DspInfo.java @@ -0,0 +1,56 @@ +/* + * 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.service.voice; + +import java.util.UUID; + +/** + * Properties of the DSP hardware on the device. + * @hide + */ +public class DspInfo { + /** + * Unique voice engine Id (changes with each version). + */ + public final UUID voiceEngineId; + + /** + * Human readable voice detection engine implementor. + */ + public final String voiceEngineImplementor; + /** + * Human readable voice detection engine description. + */ + public final String voiceEngineDescription; + /** + * Human readable voice detection engine version + */ + public final int voiceEngineVersion; + /** + * Rated power consumption when detection is active. + */ + public final int powerConsumptionMw; + + public DspInfo(UUID voiceEngineId, String voiceEngineImplementor, + String voiceEngineDescription, int version, int powerConsumptionMw) { + this.voiceEngineId = voiceEngineId; + this.voiceEngineImplementor = voiceEngineImplementor; + this.voiceEngineDescription = voiceEngineDescription; + this.voiceEngineVersion = version; + this.powerConsumptionMw = powerConsumptionMw; + } +} diff --git a/core/java/android/service/voice/KeyphraseEnrollmentInfo.java b/core/java/android/service/voice/KeyphraseEnrollmentInfo.java new file mode 100644 index 0000000..ebe41ce --- /dev/null +++ b/core/java/android/service/voice/KeyphraseEnrollmentInfo.java @@ -0,0 +1,246 @@ +/* + * 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.service.voice; + +import android.Manifest; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.util.AttributeSet; +import android.util.Slog; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.List; + +/** @hide */ +public class KeyphraseEnrollmentInfo { + private static final String TAG = "KeyphraseEnrollmentInfo"; + /** + * Name under which a Hotword enrollment component publishes information about itself. + * This meta-data should reference an XML resource containing a + * <code><{@link + * android.R.styleable#VoiceEnrollmentApplication + * voice-enrollment-application}></code> tag. + */ + private static final String VOICE_KEYPHRASE_META_DATA = "android.voice_enrollment"; + /** + * Activity Action: Show activity for managing the keyphrases for hotword detection. + * This needs to be defined by an activity that supports enrolling users for hotword/keyphrase + * detection. + */ + public static final String ACTION_MANAGE_VOICE_KEYPHRASES = + "com.android.intent.action.MANAGE_VOICE_KEYPHRASES"; + /** + * Intent extra: The intent extra for un-enrolling a user for a particular keyphrase. + */ + public static final String EXTRA_VOICE_KEYPHRASE_UNENROLL = + "com.android.intent.extra.VOICE_KEYPHRASE_UNENROLL"; + /** + * Intent extra: The hint text to be shown on the voice keyphrase management UI. + */ + public static final String EXTRA_VOICE_KEYPHRASE_HINT_TEXT = + "com.android.intent.extra.VOICE_KEYPHRASE_HINT_TEXT"; + /** + * Intent extra: The voice locale to use while managing the keyphrase. + */ + public static final String EXTRA_VOICE_KEYPHRASE_LOCALE = + "com.android.intent.extra.VOICE_KEYPHRASE_LOCALE"; + + private KeyphraseInfo[] mKeyphrases; + private String mEnrollmentPackage; + private String mParseError; + + public KeyphraseEnrollmentInfo(PackageManager pm) { + // Find the apps that supports enrollment for hotword keyhphrases, + // Pick a privileged app and obtain the information about the supported keyphrases + // from its metadata. + List<ResolveInfo> ris = pm.queryIntentActivities( + new Intent(ACTION_MANAGE_VOICE_KEYPHRASES), PackageManager.MATCH_DEFAULT_ONLY); + if (ris == null || ris.isEmpty()) { + // No application capable of enrolling for voice keyphrases is present. + mParseError = "No enrollment application found"; + return; + } + + boolean found = false; + ApplicationInfo ai = null; + for (ResolveInfo ri : ris) { + try { + ai = pm.getApplicationInfo( + ri.activityInfo.packageName, PackageManager.GET_META_DATA); + if ((ai.flags & ApplicationInfo.FLAG_PRIVILEGED) == 0) { + // The application isn't privileged (/system/priv-app). + // The enrollment application needs to be a privileged system app. + Slog.w(TAG, ai.packageName + "is not a privileged system app"); + continue; + } + if (!Manifest.permission.MANAGE_VOICE_KEYPHRASES.equals(ai.permission)) { + // The application trying to manage keyphrases doesn't + // require the MANAGE_VOICE_KEYPHRASES permission. + Slog.w(TAG, ai.packageName + " does not require MANAGE_VOICE_KEYPHRASES"); + continue; + } + mEnrollmentPackage = ai.packageName; + found = true; + break; + } catch (PackageManager.NameNotFoundException e) { + Slog.w(TAG, "error parsing voice enrollment meta-data", e); + } + } + + if (!found) { + mKeyphrases = null; + mParseError = "No suitable enrollment application found"; + return; + } + + XmlResourceParser parser = null; + try { + parser = ai.loadXmlMetaData(pm, VOICE_KEYPHRASE_META_DATA); + if (parser == null) { + mParseError = "No " + VOICE_KEYPHRASE_META_DATA + " meta-data for " + + ai.packageName; + return; + } + + Resources res = pm.getResourcesForApplication(ai); + AttributeSet attrs = Xml.asAttributeSet(parser); + + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + } + + String nodeName = parser.getName(); + if (!"voice-enrollment-application".equals(nodeName)) { + mParseError = "Meta-data does not start with voice-enrollment-application tag"; + return; + } + + TypedArray array = res.obtainAttributes(attrs, + com.android.internal.R.styleable.VoiceEnrollmentApplication); + int searchKeyphraseId = array.getInt( + com.android.internal.R.styleable.VoiceEnrollmentApplication_searchKeyphraseId, + -1); + if (searchKeyphraseId != -1) { + String searchKeyphrase = array.getString(com.android.internal.R.styleable + .VoiceEnrollmentApplication_searchKeyphrase); + String searchKeyphraseSupportedLocales = + array.getString(com.android.internal.R.styleable + .VoiceEnrollmentApplication_searchKeyphraseSupportedLocales); + String[] supportedLocales = new String[0]; + // Get all the supported locales from the comma-delimted string. + if (searchKeyphraseSupportedLocales != null + && !searchKeyphraseSupportedLocales.isEmpty()) { + supportedLocales = searchKeyphraseSupportedLocales.split(","); + } + mKeyphrases = new KeyphraseInfo[1]; + mKeyphrases[0] = new KeyphraseInfo( + searchKeyphraseId, searchKeyphrase, supportedLocales); + } else { + mParseError = "searchKeyphraseId not specified in meta-data"; + return; + } + } catch (XmlPullParserException e) { + mParseError = "Error parsing keyphrase enrollment meta-data: " + e; + Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e); + return; + } catch (IOException e) { + mParseError = "Error parsing keyphrase enrollment meta-data: " + e; + Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e); + return; + } catch (PackageManager.NameNotFoundException e) { + mParseError = "Error parsing keyphrase enrollment meta-data: " + e; + Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e); + return; + } finally { + if (parser != null) parser.close(); + } + } + + public String getParseError() { + return mParseError; + } + + /** + * @return An array of available keyphrases that can be enrolled on the system. + * It may be null if no keyphrases can be enrolled. + */ + public KeyphraseInfo[] getKeyphrases() { + return mKeyphrases; + } + + /** + * Returns an intent to launch an activity that manages the given keyphrase + * for the locale. + * + * @param enroll Indicates if the intent should enroll the user or un-enroll them. + * @param keyphrase The keyphrase that the user needs to be enrolled to. + * @param locale The locale for which the enrollment needs to be performed. + * @return An {@link Intent} to manage the keyphrase. This can be null if managing the + * given keyphrase/locale combination isn't possible. + */ + public Intent getManageKeyphraseIntent(boolean enroll, String keyphrase, String locale) { + if (mEnrollmentPackage == null || mEnrollmentPackage.isEmpty()) { + Slog.w(TAG, "No enrollment application exists"); + return null; + } + + if (isKeyphraseEnrollmentSupported(keyphrase, locale)) { + Intent intent = new Intent(ACTION_MANAGE_VOICE_KEYPHRASES) + .setPackage(mEnrollmentPackage) + .putExtra(EXTRA_VOICE_KEYPHRASE_HINT_TEXT, keyphrase) + .putExtra(EXTRA_VOICE_KEYPHRASE_LOCALE, locale); + if (!enroll) intent.putExtra(EXTRA_VOICE_KEYPHRASE_UNENROLL, true); + return intent; + } + return null; + } + + /** + * Indicates if enrollment is supported for the given keyphrase & locale. + * + * @param keyphrase The keyphrase that the user needs to be enrolled to. + * @param locale The locale for which the enrollment needs to be performed. + * @return true, if an enrollment client supports the given keyphrase and the given locale. + */ + public boolean isKeyphraseEnrollmentSupported(String keyphrase, String locale) { + if (mKeyphrases == null || mKeyphrases.length == 0) { + Slog.w(TAG, "Enrollment application doesn't support keyphrases"); + return false; + } + for (KeyphraseInfo keyphraseInfo : mKeyphrases) { + // Check if the given keyphrase is supported in the locale provided by + // the enrollment application. + String supportedKeyphrase = keyphraseInfo.keyphrase; + if (supportedKeyphrase.equalsIgnoreCase(keyphrase) + && keyphraseInfo.supportedLocales.contains(locale)) { + return true; + } + } + Slog.w(TAG, "Enrollment application doesn't support the given keyphrase"); + return false; + } +} diff --git a/core/java/android/service/voice/KeyphraseInfo.java b/core/java/android/service/voice/KeyphraseInfo.java new file mode 100644 index 0000000..d266e1a --- /dev/null +++ b/core/java/android/service/voice/KeyphraseInfo.java @@ -0,0 +1,27 @@ +package android.service.voice; + +import android.util.ArraySet; + +/** + * A Voice Keyphrase. + * @hide + */ +public class KeyphraseInfo { + public final int id; + public final String keyphrase; + public final ArraySet<String> supportedLocales; + + public KeyphraseInfo(int id, String keyphrase, String[] supportedLocales) { + this.id = id; + this.keyphrase = keyphrase; + this.supportedLocales = new ArraySet<String>(supportedLocales.length); + for (String locale : supportedLocales) { + this.supportedLocales.add(locale); + } + } + + @Override + public String toString() { + return "id=" + id + ", keyphrase=" + keyphrase + ", supported-locales=" + supportedLocales; + } +} diff --git a/core/java/android/service/voice/SoundTriggerManager.java b/core/java/android/service/voice/SoundTriggerManager.java new file mode 100644 index 0000000..2d049b9 --- /dev/null +++ b/core/java/android/service/voice/SoundTriggerManager.java @@ -0,0 +1,73 @@ +/* + * 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.service.voice; + +import android.hardware.soundtrigger.SoundTrigger; +import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; + +import java.util.ArrayList; + +/** + * Manager for {@link SoundTrigger} APIs. + * Currently this just acts as an abstraction over all SoundTrigger API calls. + * @hide + */ +public class SoundTriggerManager { + /** The {@link DspInfo} for the system, or null if none exists. */ + public DspInfo dspInfo; + + public SoundTriggerManager() { + ArrayList <ModuleProperties> modules = new ArrayList<>(); + int status = SoundTrigger.listModules(modules); + if (status != SoundTrigger.STATUS_OK || modules.size() == 0) { + // TODO(sansid, elaurent): Figure out how to handle errors in listing the modules here. + dspInfo = null; + } else { + // TODO(sansid, elaurent): Figure out how to determine which module corresponds to the + // DSP hardware. + ModuleProperties properties = modules.get(0); + dspInfo = new DspInfo(properties.uuid, properties.implementor, properties.description, + properties.version, properties.powerConsumptionMw); + } + } + + /** + * @return True, if the keyphrase is supported on DSP for the given locale. + */ + public boolean isKeyphraseSupported(String keyphrase, String locale) { + // TODO(sansid): We also need to look into a SoundTrigger API that let's us + // query this. For now just return supported if there's a DSP available. + return dspInfo != null; + } + + /** + * @return True, if the keyphrase is has been enrolled for the given locale. + */ + public boolean isKeyphraseEnrolled(String keyphrase, String locale) { + // TODO(sansid, elaurent): Query SoundTrigger to list currently loaded sound models. + // They have been enrolled. + return false; + } + + /** + * @return True, if a recognition for the keyphrase is active for the given locale. + */ + public boolean isKeyphraseActive(String keyphrase, String locale) { + // TODO(sansid, elaurent): Check if the recognition for the keyphrase is currently active. + return false; + } +} diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index e15489b..e0329f8 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -17,7 +17,6 @@ package android.service.voice; import android.annotation.SdkConstant; -import android.app.Instrumentation; import android.app.Service; import android.content.Context; import android.content.Intent; @@ -25,8 +24,11 @@ import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; + +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceInteractionManagerService; + /** * Top-level service of the current global voice interactor, which is providing * support for hotwording, the back-end of a {@link android.app.VoiceInteractor}, etc. @@ -51,6 +53,16 @@ public class VoiceInteractionService extends Service { public static final String SERVICE_INTERFACE = "android.service.voice.VoiceInteractionService"; + // TODO(sansid): Unhide these. + /** @hide */ + public static final int KEYPHRASE_UNAVAILABLE = 0; + /** @hide */ + public static final int KEYPHRASE_UNENROLLED = 1; + /** @hide */ + public static final int KEYPHRASE_ENROLLED = 2; + /** @hide */ + public static final int KEYPHRASE_ACTIVE = 3; + /** * Name under which a VoiceInteractionService component publishes information about itself. * This meta-data should reference an XML resource containing a @@ -64,6 +76,9 @@ public class VoiceInteractionService extends Service { IVoiceInteractionManagerService mSystemService; + private SoundTriggerManager mSoundTriggerManager; + private KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo; + public void startSession(Bundle args) { try { mSystemService.startSession(mInterface, args); @@ -76,6 +91,8 @@ public class VoiceInteractionService extends Service { super.onCreate(); mSystemService = IVoiceInteractionManagerService.Stub.asInterface( ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE)); + mKeyphraseEnrollmentInfo = new KeyphraseEnrollmentInfo(getPackageManager()); + mSoundTriggerManager = new SoundTriggerManager(); } @Override @@ -85,4 +102,44 @@ public class VoiceInteractionService extends Service { } return null; } + + /** + * Gets the state of always-on hotword detection for the given keyphrase and locale + * on this system. + * Availability implies that the hardware on this system is capable of listening for + * the given keyphrase or not. + * The return code is one of {@link #KEYPHRASE_UNAVAILABLE}, {@link #KEYPHRASE_UNENROLLED} + * {@link #KEYPHRASE_ENROLLED} or {@link #KEYPHRASE_ACTIVE}. + * + * @param keyphrase The keyphrase whose availability is being checked. + * @param locale The locale for which the availability is being checked. + * @return Indicates if always-on hotword detection is available for the given keyphrase. + * TODO(sansid): Unhide this. + * @hide + */ + public final int getAlwaysOnKeyphraseAvailability(String keyphrase, String locale) { + // The available keyphrases is a combination of DSP availability and + // the keyphrases that have an enrollment application for them. + if (!mSoundTriggerManager.isKeyphraseSupported(keyphrase, locale) + || !mKeyphraseEnrollmentInfo.isKeyphraseEnrollmentSupported(keyphrase, locale)) { + return KEYPHRASE_UNAVAILABLE; + } + if (!mSoundTriggerManager.isKeyphraseEnrolled(keyphrase, locale)) { + return KEYPHRASE_UNENROLLED; + } + if (!mSoundTriggerManager.isKeyphraseActive(keyphrase, locale)) { + return KEYPHRASE_ENROLLED; + } else { + return KEYPHRASE_ACTIVE; + } + } + + /** + * @return Details of keyphrases available for enrollment. + * @hide + */ + @VisibleForTesting + protected final KeyphraseEnrollmentInfo getKeyphraseEnrollmentInfo() { + return mKeyphraseEnrollmentInfo; + } } 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/RequestConfig.java b/core/java/android/speech/tts/RequestConfig.java index 4b5385f..84880c0 100644 --- a/core/java/android/speech/tts/RequestConfig.java +++ b/core/java/android/speech/tts/RequestConfig.java @@ -1,3 +1,18 @@ +/* + * 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.speech.tts; import android.media.AudioManager; diff --git a/core/java/android/speech/tts/RequestConfigHelper.java b/core/java/android/speech/tts/RequestConfigHelper.java index b25c985..3b5490b 100644 --- a/core/java/android/speech/tts/RequestConfigHelper.java +++ b/core/java/android/speech/tts/RequestConfigHelper.java @@ -1,3 +1,18 @@ +/* + * 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.speech.tts; import android.speech.tts.TextToSpeechClient.EngineStatus; diff --git a/core/java/android/speech/tts/SynthesisRequestV2.java b/core/java/android/speech/tts/SynthesisRequestV2.java index a1da49c..a42aa16 100644 --- a/core/java/android/speech/tts/SynthesisRequestV2.java +++ b/core/java/android/speech/tts/SynthesisRequestV2.java @@ -1,14 +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.speech.tts; 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 +32,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 +53,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 +68,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 +93,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 +104,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..0c0be83 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. @@ -794,15 +793,14 @@ public class TextToSpeechClient { return mService != null && mEstablished; } - boolean runAction(Action action) { + <T> ActionResult<T> runAction(Action<T> action) { synchronized (mLock) { try { - action.run(mService); - return true; + return new ActionResult<T>(true, action.run(mService)); } catch (Exception ex) { Log.e(TAG, action.getName() + " failed", ex); disconnect(); - return false; + return new ActionResult<T>(false); } } } @@ -822,7 +820,7 @@ public class TextToSpeechClient { } } - private abstract class Action { + private abstract class Action<T> { private final String mName; public Action(String name) { @@ -830,7 +828,21 @@ public class TextToSpeechClient { } public String getName() {return mName;} - abstract void run(ITextToSpeechService service) throws RemoteException; + abstract T run(ITextToSpeechService service) throws RemoteException; + } + + private class ActionResult<T> { + boolean mSuccess; + T mResult; + + ActionResult(boolean success) { + mSuccess = success; + } + + ActionResult(boolean success, T result) { + mSuccess = success; + mResult = result; + } } private IBinder getCallerIdentity() { @@ -840,18 +852,17 @@ public class TextToSpeechClient { return null; } - private boolean runAction(Action action) { + private <T> ActionResult<T> runAction(Action<T> action) { synchronized (mLock) { if (mServiceConnection == null) { Log.w(TAG, action.getName() + " failed: not bound to TTS engine"); - return false; + return new ActionResult<T>(false); } if (!mServiceConnection.isEstablished()) { Log.w(TAG, action.getName() + " failed: not fully bound to TTS engine"); - return false; + return new ActionResult<T>(false); } - mServiceConnection.runAction(action); - return true; + return mServiceConnection.runAction(action); } } @@ -862,13 +873,14 @@ public class TextToSpeechClient { * other utterances in the queue. */ public void stop() { - runAction(new Action(ACTION_STOP_NAME) { + runAction(new Action<Void>(ACTION_STOP_NAME) { @Override - public void run(ITextToSpeechService service) throws RemoteException { + public Void run(ITextToSpeechService service) throws RemoteException { if (service.stop(getCallerIdentity()) != Status.SUCCESS) { Log.e(TAG, "Stop failed"); } mCallbacks.clear(); + return null; } }); } @@ -876,7 +888,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,15 +899,38 @@ 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) { - runAction(new Action(ACTION_QUEUE_SPEAK_NAME) { + 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<Void>(ACTION_QUEUE_SPEAK_NAME) { @Override - public void run(ITextToSpeechService service) throws RemoteException { + public Void run(ITextToSpeechService service) throws RemoteException { RequestCallbacks c = mDefaultRequestCallbacks; if (callbacks != null) { c = callbacks; @@ -903,15 +938,16 @@ public class TextToSpeechClient { int addCallbackStatus = addCallback(utteranceId, c); if (addCallbackStatus != Status.SUCCESS) { c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST); - return; + return null; } 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); } + return null; } }); } @@ -931,15 +967,40 @@ 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) { - runAction(new Action(ACTION_QUEUE_SYNTHESIZE_TO_FILE) { + 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<Void>(ACTION_QUEUE_SYNTHESIZE_TO_FILE) { @Override - public void run(ITextToSpeechService service) throws RemoteException { + public Void run(ITextToSpeechService service) throws RemoteException { RequestCallbacks c = mDefaultRequestCallbacks; if (callbacks != null) { c = callbacks; @@ -947,7 +1008,7 @@ public class TextToSpeechClient { int addCallbackStatus = addCallback(utteranceId, c); if (addCallbackStatus != Status.SUCCESS) { c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST); - return; + return null; } ParcelFileDescriptor fileDescriptor = null; @@ -955,7 +1016,7 @@ public class TextToSpeechClient { if (outputFile.exists() && !outputFile.canWrite()) { Log.e(TAG, "No permissions to write to " + outputFile); removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT); - return; + return null; } fileDescriptor = ParcelFileDescriptor.open(outputFile, ParcelFileDescriptor.MODE_WRITE_ONLY | @@ -964,8 +1025,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); @@ -977,10 +1037,18 @@ public class TextToSpeechClient { Log.e(TAG, "Closing file " + outputFile + " failed", e); removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT); } + return null; } }); } + 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"; /** @@ -997,9 +1065,9 @@ public class TextToSpeechClient { */ public void queueSilence(final long durationInMs, final UtteranceId utteranceId, final RequestCallbacks callbacks) { - runAction(new Action(ACTION_QUEUE_SILENCE_NAME) { + runAction(new Action<Void>(ACTION_QUEUE_SILENCE_NAME) { @Override - public void run(ITextToSpeechService service) throws RemoteException { + public Void run(ITextToSpeechService service) throws RemoteException { RequestCallbacks c = mDefaultRequestCallbacks; if (callbacks != null) { c = callbacks; @@ -1015,6 +1083,7 @@ public class TextToSpeechClient { if (queueResult != Status.SUCCESS) { removeCallbackAndErr(utteranceId.toUniqueString(), queueResult); } + return null; } }); } @@ -1038,9 +1107,9 @@ public class TextToSpeechClient { */ public void queueAudio(final Uri audioUrl, final UtteranceId utteranceId, final RequestConfig config, final RequestCallbacks callbacks) { - runAction(new Action(ACTION_QUEUE_AUDIO_NAME) { + runAction(new Action<Void>(ACTION_QUEUE_AUDIO_NAME) { @Override - public void run(ITextToSpeechService service) throws RemoteException { + public Void run(ITextToSpeechService service) throws RemoteException { RequestCallbacks c = mDefaultRequestCallbacks; if (callbacks != null) { c = callbacks; @@ -1056,10 +1125,35 @@ public class TextToSpeechClient { if (queueResult != Status.SUCCESS) { removeCallbackAndErr(utteranceId.toUniqueString(), queueResult); } + return null; } }); } + private static final String ACTION_IS_SPEAKING_NAME = "isSpeaking"; + + /** + * Checks whether the TTS engine is busy speaking. Note that a speech item is + * considered complete once it's audio data has been sent to the audio mixer, or + * written to a file. There might be a finite lag between this point, and when + * the audio hardware completes playback. + * + * @return {@code true} if the TTS engine is speaking. + */ + public boolean isSpeaking() { + ActionResult<Boolean> result = runAction(new Action<Boolean>(ACTION_IS_SPEAKING_NAME) { + @Override + public Boolean run(ITextToSpeechService service) throws RemoteException { + return service.isSpeaking(); + } + }); + if (!result.mSuccess) { + return false; // We can't really say, return false + } + return result.mResult; + } + + class InternalHandler extends Handler { final static int WHAT_ENGINE_STATUS_CHANGED = 1; final static int WHAT_SERVICE_DISCONNECTED = 2; 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/speech/tts/VoiceInfo.java b/core/java/android/speech/tts/VoiceInfo.java index 16b9a97..71629dc 100644 --- a/core/java/android/speech/tts/VoiceInfo.java +++ b/core/java/android/speech/tts/VoiceInfo.java @@ -1,3 +1,18 @@ +/* + * 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.speech.tts; import android.os.Bundle; diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index f06ae71..48122d6 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -48,10 +48,11 @@ import android.text.style.URLSpan; import android.text.style.UnderlineSpan; import android.util.Log; import android.util.Printer; - import android.view.View; + import com.android.internal.R; import com.android.internal.util.ArrayUtils; + import libcore.icu.ICU; import java.lang.reflect.Array; @@ -229,7 +230,12 @@ public class TextUtils { public static boolean regionMatches(CharSequence one, int toffset, CharSequence two, int ooffset, int len) { - char[] temp = obtain(2 * len); + int tempLen = 2 * len; + if (tempLen < len) { + // Integer overflow; len is unreasonably large + throw new IndexOutOfBoundsException(); + } + char[] temp = obtain(tempLen); getChars(one, toffset, toffset + len, temp, 0); getChars(two, ooffset, ooffset + len, temp, len); diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java index deb138d..c1341e1 100644 --- a/core/java/android/text/util/Linkify.java +++ b/core/java/android/text/util/Linkify.java @@ -465,32 +465,39 @@ public class Linkify { String address; int base = 0; - while ((address = WebView.findAddress(string)) != null) { - int start = string.indexOf(address); + try { + while ((address = WebView.findAddress(string)) != null) { + int start = string.indexOf(address); - if (start < 0) { - break; - } + if (start < 0) { + break; + } - LinkSpec spec = new LinkSpec(); - int length = address.length(); - int end = start + length; - - spec.start = base + start; - spec.end = base + end; - string = string.substring(end); - base += end; - - String encodedAddress = null; - - try { - encodedAddress = URLEncoder.encode(address,"UTF-8"); - } catch (UnsupportedEncodingException e) { - continue; - } + LinkSpec spec = new LinkSpec(); + int length = address.length(); + int end = start + length; - spec.url = "geo:0,0?q=" + encodedAddress; - links.add(spec); + spec.start = base + start; + spec.end = base + end; + string = string.substring(end); + base += end; + + String encodedAddress = null; + + try { + encodedAddress = URLEncoder.encode(address,"UTF-8"); + } catch (UnsupportedEncodingException e) { + continue; + } + + spec.url = "geo:0,0?q=" + encodedAddress; + links.add(spec); + } + } catch (UnsupportedOperationException e) { + // findAddress may fail with an unsupported exception on platforms without a WebView. + // In this case, we will not append anything to the links variable: it would have died + // in WebView.findAddress. + return; } } diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index e9c2bba..0a4f641 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -603,76 +603,76 @@ public abstract class Transition implements Cloneable { for (int i = 0; i < startValuesList.size(); ++i) { TransitionValues start = startValuesList.get(i); TransitionValues end = endValuesList.get(i); - // Only bother trying to animate with values that differ between start/end - if (start != null || end != null) { - if (start == null || !start.equals(end)) { - if (DBG) { - View view = (end != null) ? end.view : start.view; - Log.d(LOG_TAG, " differing start/end values for view " + - view); - if (start == null || end == null) { - Log.d(LOG_TAG, " " + ((start == null) ? - "start null, end non-null" : "start non-null, end null")); - } else { - for (String key : start.values.keySet()) { - Object startValue = start.values.get(key); - Object endValue = end.values.get(key); - if (startValue != endValue && !startValue.equals(endValue)) { - Log.d(LOG_TAG, " " + key + ": start(" + startValue + - "), end(" + endValue +")"); - } + // Only bother trying to animate with valid values that differ between start/end + boolean isInvalidStart = start != null && !isValidTarget(start.view); + boolean isInvalidEnd = end != null && !isValidTarget(end.view); + boolean isChanged = start != end && (start == null || !start.equals(end)); + if (isChanged && !isInvalidStart && !isInvalidEnd) { + if (DBG) { + View view = (end != null) ? end.view : start.view; + Log.d(LOG_TAG, " differing start/end values for view " + view); + if (start == null || end == null) { + Log.d(LOG_TAG, " " + ((start == null) ? + "start null, end non-null" : "start non-null, end null")); + } else { + for (String key : start.values.keySet()) { + Object startValue = start.values.get(key); + Object endValue = end.values.get(key); + if (startValue != endValue && !startValue.equals(endValue)) { + Log.d(LOG_TAG, " " + key + ": start(" + startValue + + "), end(" + endValue + ")"); } } } - // TODO: what to do about targetIds and itemIds? - Animator animator = createAnimator(sceneRoot, start, end); - if (animator != null) { - // Save animation info for future cancellation purposes - View view = null; - TransitionValues infoValues = null; - if (end != null) { - view = end.view; - String[] properties = getTransitionProperties(); - if (view != null && properties != null && properties.length > 0) { - infoValues = new TransitionValues(); - infoValues.view = view; - TransitionValues newValues = endValues.viewValues.get(view); - if (newValues != null) { - for (int j = 0; j < properties.length; ++j) { - infoValues.values.put(properties[j], - newValues.values.get(properties[j])); - } + } + // TODO: what to do about targetIds and itemIds? + Animator animator = createAnimator(sceneRoot, start, end); + if (animator != null) { + // Save animation info for future cancellation purposes + View view = null; + TransitionValues infoValues = null; + if (end != null) { + view = end.view; + String[] properties = getTransitionProperties(); + if (view != null && properties != null && properties.length > 0) { + infoValues = new TransitionValues(); + infoValues.view = view; + TransitionValues newValues = endValues.viewValues.get(view); + if (newValues != null) { + for (int j = 0; j < properties.length; ++j) { + infoValues.values.put(properties[j], + newValues.values.get(properties[j])); } - int numExistingAnims = runningAnimators.size(); - for (int j = 0; j < numExistingAnims; ++j) { - Animator anim = runningAnimators.keyAt(j); - AnimationInfo info = runningAnimators.get(anim); - if (info.values != null && info.view == view && - ((info.name == null && getName() == null) || - info.name.equals(getName()))) { - if (info.values.equals(infoValues)) { - // Favor the old animator - animator = null; - break; - } + } + int numExistingAnims = runningAnimators.size(); + for (int j = 0; j < numExistingAnims; ++j) { + Animator anim = runningAnimators.keyAt(j); + AnimationInfo info = runningAnimators.get(anim); + if (info.values != null && info.view == view && + ((info.name == null && getName() == null) || + info.name.equals(getName()))) { + if (info.values.equals(infoValues)) { + // Favor the old animator + animator = null; + break; } } } - } else { - view = (start != null) ? start.view : null; } - if (animator != null) { - if (mPropagation != null) { - long delay = mPropagation - .getStartDelay(sceneRoot, this, start, end); - startDelays.put(mAnimators.size(), delay); - minStartDelay = Math.min(delay, minStartDelay); - } - AnimationInfo info = new AnimationInfo(view, getName(), - sceneRoot.getWindowId(), infoValues); - runningAnimators.put(animator, info); - mAnimators.add(animator); + } else { + view = (start != null) ? start.view : null; + } + if (animator != null) { + if (mPropagation != null) { + long delay = mPropagation + .getStartDelay(sceneRoot, this, start, end); + startDelays.put(mAnimators.size(), delay); + minStartDelay = Math.min(delay, minStartDelay); } + AnimationInfo info = new AnimationInfo(view, getName(), + sceneRoot.getWindowId(), infoValues); + runningAnimators.put(animator, info); + mAnimators.add(animator); } } } 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..b5b9199 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); @@ -254,8 +217,6 @@ final class HardwareLayer { private static native void nUpdateRenderLayer(long layerUpdater, long displayList, int left, int top, int right, int bottom); - private static native boolean nFlushChanges(long layerUpdater); - private static native long nGetLayer(long layerUpdater); private static native int nGetTexName(long layerUpdater); } diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index d71de9f..592dec8 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()); } /** @@ -366,8 +363,7 @@ public abstract class HardwareRenderer { * @param callbacks Callbacks invoked when drawing happens. * @param dirty The dirty rectangle to update, can be null. */ - abstract void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, - Rect dirty); + abstract void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks); /** * Creates a new hardware layer. A hardware layer built by calling this @@ -469,11 +465,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 +492,7 @@ public abstract class HardwareRenderer { * see {@link android.content.ComponentCallbacks} */ static void startTrimMemory(int level) { - GLRenderer.startTrimMemory(level); + ThreadedRenderer.startTrimMemory(level); } /** @@ -508,7 +500,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/KeyEvent.java b/core/java/android/view/KeyEvent.java index 8a996d2..8b2ec7a 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -1716,6 +1716,7 @@ public class KeyEvent extends InputEvent implements Parcelable { case KeyEvent.KEYCODE_MENU: case KeyEvent.KEYCODE_SLEEP: case KeyEvent.KEYCODE_WAKEUP: + case KeyEvent.KEYCODE_PAIRING: return true; } return false; diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java index e63829e..c165475 100644 --- a/core/java/android/view/RenderNode.java +++ b/core/java/android/view/RenderNode.java @@ -325,8 +325,8 @@ public class RenderNode { * * @hide */ - public void setCaching(boolean caching) { - nSetCaching(mNativeRenderNode, caching); + public boolean setCaching(boolean caching) { + return nSetCaching(mNativeRenderNode, caching); } /** @@ -335,8 +335,8 @@ public class RenderNode { * * @param clipToBounds true if the display list should clip to its bounds */ - public void setClipToBounds(boolean clipToBounds) { - nSetClipToBounds(mNativeRenderNode, clipToBounds); + public boolean setClipToBounds(boolean clipToBounds) { + return nSetClipToBounds(mNativeRenderNode, clipToBounds); } /** @@ -346,8 +346,8 @@ public class RenderNode { * @param shouldProject true if the display list should be projected onto a * containing volume. */ - public void setProjectBackwards(boolean shouldProject) { - nSetProjectBackwards(mNativeRenderNode, shouldProject); + public boolean setProjectBackwards(boolean shouldProject) { + return nSetProjectBackwards(mNativeRenderNode, shouldProject); } /** @@ -355,8 +355,8 @@ public class RenderNode { * DisplayList should draw any descendent DisplayLists with * ProjectBackwards=true directly on top of it. Default value is false. */ - public void setProjectionReceiver(boolean shouldRecieve) { - nSetProjectionReceiver(mNativeRenderNode, shouldRecieve); + public boolean setProjectionReceiver(boolean shouldRecieve) { + return nSetProjectionReceiver(mNativeRenderNode, shouldRecieve); } /** @@ -365,15 +365,16 @@ public class RenderNode { * * Deep copies the data into native to simplify reference ownership. */ - public void setOutline(Outline outline) { + public boolean setOutline(Outline outline) { if (outline == null || outline.isEmpty()) { - nSetOutlineEmpty(mNativeRenderNode); + return nSetOutlineEmpty(mNativeRenderNode); } else if (outline.mRect != null) { - nSetOutlineRoundRect(mNativeRenderNode, outline.mRect.left, outline.mRect.top, + return nSetOutlineRoundRect(mNativeRenderNode, outline.mRect.left, outline.mRect.top, outline.mRect.right, outline.mRect.bottom, outline.mRadius); } else if (outline.mPath != null) { - nSetOutlineConvexPath(mNativeRenderNode, outline.mPath.mNativePath); + return nSetOutlineConvexPath(mNativeRenderNode, outline.mPath.mNativePath); } + throw new IllegalArgumentException("Unrecognized outline?"); } /** @@ -381,8 +382,8 @@ public class RenderNode { * * @param clipToOutline true if clipping to the outline. */ - public void setClipToOutline(boolean clipToOutline) { - nSetClipToOutline(mNativeRenderNode, clipToOutline); + public boolean setClipToOutline(boolean clipToOutline) { + return nSetClipToOutline(mNativeRenderNode, clipToOutline); } public boolean getClipToOutline() { @@ -392,9 +393,9 @@ public class RenderNode { /** * Controls the RenderNode's circular reveal clip. */ - public void setRevealClip(boolean shouldClip, boolean inverseClip, + public boolean setRevealClip(boolean shouldClip, boolean inverseClip, float x, float y, float radius) { - nSetRevealClip(mNativeRenderNode, shouldClip, inverseClip, x, y, radius); + return nSetRevealClip(mNativeRenderNode, shouldClip, inverseClip, x, y, radius); } /** @@ -403,8 +404,8 @@ public class RenderNode { * * @param matrix A transform matrix to apply to this display list */ - public void setStaticMatrix(Matrix matrix) { - nSetStaticMatrix(mNativeRenderNode, matrix.native_instance); + public boolean setStaticMatrix(Matrix matrix) { + return nSetStaticMatrix(mNativeRenderNode, matrix.native_instance); } /** @@ -417,8 +418,8 @@ public class RenderNode { * * @hide */ - public void setAnimationMatrix(Matrix matrix) { - nSetAnimationMatrix(mNativeRenderNode, + public boolean setAnimationMatrix(Matrix matrix) { + return nSetAnimationMatrix(mNativeRenderNode, (matrix != null) ? matrix.native_instance : 0); } @@ -430,8 +431,8 @@ public class RenderNode { * @see View#setAlpha(float) * @see #getAlpha() */ - public void setAlpha(float alpha) { - nSetAlpha(mNativeRenderNode, alpha); + public boolean setAlpha(float alpha) { + return nSetAlpha(mNativeRenderNode, alpha); } /** @@ -456,8 +457,8 @@ public class RenderNode { * @see android.view.View#hasOverlappingRendering() * @see #hasOverlappingRendering() */ - public void setHasOverlappingRendering(boolean hasOverlappingRendering) { - nSetHasOverlappingRendering(mNativeRenderNode, hasOverlappingRendering); + public boolean setHasOverlappingRendering(boolean hasOverlappingRendering) { + return nSetHasOverlappingRendering(mNativeRenderNode, hasOverlappingRendering); } /** @@ -472,8 +473,8 @@ public class RenderNode { return nHasOverlappingRendering(mNativeRenderNode); } - public void setElevation(float lift) { - nSetElevation(mNativeRenderNode, lift); + public boolean setElevation(float lift) { + return nSetElevation(mNativeRenderNode, lift); } public float getElevation() { @@ -488,8 +489,8 @@ public class RenderNode { * @see View#setTranslationX(float) * @see #getTranslationX() */ - public void setTranslationX(float translationX) { - nSetTranslationX(mNativeRenderNode, translationX); + public boolean setTranslationX(float translationX) { + return nSetTranslationX(mNativeRenderNode, translationX); } /** @@ -509,8 +510,8 @@ public class RenderNode { * @see View#setTranslationY(float) * @see #getTranslationY() */ - public void setTranslationY(float translationY) { - nSetTranslationY(mNativeRenderNode, translationY); + public boolean setTranslationY(float translationY) { + return nSetTranslationY(mNativeRenderNode, translationY); } /** @@ -528,8 +529,8 @@ public class RenderNode { * @see View#setTranslationZ(float) * @see #getTranslationZ() */ - public void setTranslationZ(float translationZ) { - nSetTranslationZ(mNativeRenderNode, translationZ); + public boolean setTranslationZ(float translationZ) { + return nSetTranslationZ(mNativeRenderNode, translationZ); } /** @@ -549,8 +550,8 @@ public class RenderNode { * @see View#setRotation(float) * @see #getRotation() */ - public void setRotation(float rotation) { - nSetRotation(mNativeRenderNode, rotation); + public boolean setRotation(float rotation) { + return nSetRotation(mNativeRenderNode, rotation); } /** @@ -570,8 +571,8 @@ public class RenderNode { * @see View#setRotationX(float) * @see #getRotationX() */ - public void setRotationX(float rotationX) { - nSetRotationX(mNativeRenderNode, rotationX); + public boolean setRotationX(float rotationX) { + return nSetRotationX(mNativeRenderNode, rotationX); } /** @@ -591,8 +592,8 @@ public class RenderNode { * @see View#setRotationY(float) * @see #getRotationY() */ - public void setRotationY(float rotationY) { - nSetRotationY(mNativeRenderNode, rotationY); + public boolean setRotationY(float rotationY) { + return nSetRotationY(mNativeRenderNode, rotationY); } /** @@ -612,8 +613,8 @@ public class RenderNode { * @see View#setScaleX(float) * @see #getScaleX() */ - public void setScaleX(float scaleX) { - nSetScaleX(mNativeRenderNode, scaleX); + public boolean setScaleX(float scaleX) { + return nSetScaleX(mNativeRenderNode, scaleX); } /** @@ -633,8 +634,8 @@ public class RenderNode { * @see View#setScaleY(float) * @see #getScaleY() */ - public void setScaleY(float scaleY) { - nSetScaleY(mNativeRenderNode, scaleY); + public boolean setScaleY(float scaleY) { + return nSetScaleY(mNativeRenderNode, scaleY); } /** @@ -654,8 +655,8 @@ public class RenderNode { * @see View#setPivotX(float) * @see #getPivotX() */ - public void setPivotX(float pivotX) { - nSetPivotX(mNativeRenderNode, pivotX); + public boolean setPivotX(float pivotX) { + return nSetPivotX(mNativeRenderNode, pivotX); } /** @@ -675,8 +676,8 @@ public class RenderNode { * @see View#setPivotY(float) * @see #getPivotY() */ - public void setPivotY(float pivotY) { - nSetPivotY(mNativeRenderNode, pivotY); + public boolean setPivotY(float pivotY) { + return nSetPivotY(mNativeRenderNode, pivotY); } /** @@ -702,8 +703,8 @@ public class RenderNode { * @see View#setCameraDistance(float) * @see #getCameraDistance() */ - public void setCameraDistance(float distance) { - nSetCameraDistance(mNativeRenderNode, distance); + public boolean setCameraDistance(float distance) { + return nSetCameraDistance(mNativeRenderNode, distance); } /** @@ -723,8 +724,8 @@ public class RenderNode { * @see View#setLeft(int) * @see #getLeft() */ - public void setLeft(int left) { - nSetLeft(mNativeRenderNode, left); + public boolean setLeft(int left) { + return nSetLeft(mNativeRenderNode, left); } /** @@ -744,8 +745,8 @@ public class RenderNode { * @see View#setTop(int) * @see #getTop() */ - public void setTop(int top) { - nSetTop(mNativeRenderNode, top); + public boolean setTop(int top) { + return nSetTop(mNativeRenderNode, top); } /** @@ -765,8 +766,8 @@ public class RenderNode { * @see View#setRight(int) * @see #getRight() */ - public void setRight(int right) { - nSetRight(mNativeRenderNode, right); + public boolean setRight(int right) { + return nSetRight(mNativeRenderNode, right); } /** @@ -786,8 +787,8 @@ public class RenderNode { * @see View#setBottom(int) * @see #getBottom() */ - public void setBottom(int bottom) { - nSetBottom(mNativeRenderNode, bottom); + public boolean setBottom(int bottom) { + return nSetBottom(mNativeRenderNode, bottom); } /** @@ -812,8 +813,8 @@ public class RenderNode { * @see View#setRight(int) * @see View#setBottom(int) */ - public void setLeftTopRightBottom(int left, int top, int right, int bottom) { - nSetLeftTopRightBottom(mNativeRenderNode, left, top, right, bottom); + public boolean setLeftTopRightBottom(int left, int top, int right, int bottom) { + return nSetLeftTopRightBottom(mNativeRenderNode, left, top, right, bottom); } /** @@ -824,8 +825,8 @@ public class RenderNode { * * @see View#offsetLeftAndRight(int) */ - public void offsetLeftAndRight(float offset) { - nOffsetLeftAndRight(mNativeRenderNode, offset); + public boolean offsetLeftAndRight(float offset) { + return nOffsetLeftAndRight(mNativeRenderNode, offset); } /** @@ -836,8 +837,8 @@ public class RenderNode { * * @see View#offsetTopAndBottom(int) */ - public void offsetTopAndBottom(float offset) { - nOffsetTopAndBottom(mNativeRenderNode, offset); + public boolean offsetTopAndBottom(float offset) { + return nOffsetTopAndBottom(mNativeRenderNode, offset); } /** @@ -890,42 +891,42 @@ public class RenderNode { // Properties - private static native void nOffsetTopAndBottom(long renderNode, float offset); - private static native void nOffsetLeftAndRight(long renderNode, float offset); - private static native void nSetLeftTopRightBottom(long renderNode, int left, int top, + private static native boolean nOffsetTopAndBottom(long renderNode, float offset); + private static native boolean nOffsetLeftAndRight(long renderNode, float offset); + private static native boolean nSetLeftTopRightBottom(long renderNode, int left, int top, int right, int bottom); - private static native void nSetBottom(long renderNode, int bottom); - private static native void nSetRight(long renderNode, int right); - private static native void nSetTop(long renderNode, int top); - private static native void nSetLeft(long renderNode, int left); - private static native void nSetCameraDistance(long renderNode, float distance); - private static native void nSetPivotY(long renderNode, float pivotY); - private static native void nSetPivotX(long renderNode, float pivotX); - private static native void nSetCaching(long renderNode, boolean caching); - private static native void nSetClipToBounds(long renderNode, boolean clipToBounds); - private static native void nSetProjectBackwards(long renderNode, boolean shouldProject); - private static native void nSetProjectionReceiver(long renderNode, boolean shouldRecieve); - private static native void nSetOutlineRoundRect(long renderNode, int left, int top, + private static native boolean nSetBottom(long renderNode, int bottom); + private static native boolean nSetRight(long renderNode, int right); + private static native boolean nSetTop(long renderNode, int top); + private static native boolean nSetLeft(long renderNode, int left); + private static native boolean nSetCameraDistance(long renderNode, float distance); + private static native boolean nSetPivotY(long renderNode, float pivotY); + private static native boolean nSetPivotX(long renderNode, float pivotX); + private static native boolean nSetCaching(long renderNode, boolean caching); + private static native boolean nSetClipToBounds(long renderNode, boolean clipToBounds); + private static native boolean nSetProjectBackwards(long renderNode, boolean shouldProject); + private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldRecieve); + private static native boolean nSetOutlineRoundRect(long renderNode, int left, int top, int right, int bottom, float radius); - private static native void nSetOutlineConvexPath(long renderNode, long nativePath); - private static native void nSetOutlineEmpty(long renderNode); - private static native void nSetClipToOutline(long renderNode, boolean clipToOutline); - private static native void nSetRevealClip(long renderNode, + private static native boolean nSetOutlineConvexPath(long renderNode, long nativePath); + private static native boolean nSetOutlineEmpty(long renderNode); + private static native boolean nSetClipToOutline(long renderNode, boolean clipToOutline); + private static native boolean nSetRevealClip(long renderNode, boolean shouldClip, boolean inverseClip, float x, float y, float radius); - private static native void nSetAlpha(long renderNode, float alpha); - private static native void nSetHasOverlappingRendering(long renderNode, + private static native boolean nSetAlpha(long renderNode, float alpha); + private static native boolean nSetHasOverlappingRendering(long renderNode, boolean hasOverlappingRendering); - private static native void nSetElevation(long renderNode, float lift); - private static native void nSetTranslationX(long renderNode, float translationX); - private static native void nSetTranslationY(long renderNode, float translationY); - private static native void nSetTranslationZ(long renderNode, float translationZ); - private static native void nSetRotation(long renderNode, float rotation); - private static native void nSetRotationX(long renderNode, float rotationX); - private static native void nSetRotationY(long renderNode, float rotationY); - private static native void nSetScaleX(long renderNode, float scaleX); - private static native void nSetScaleY(long renderNode, float scaleY); - private static native void nSetStaticMatrix(long renderNode, long nativeMatrix); - private static native void nSetAnimationMatrix(long renderNode, long animationMatrix); + private static native boolean nSetElevation(long renderNode, float lift); + private static native boolean nSetTranslationX(long renderNode, float translationX); + private static native boolean nSetTranslationY(long renderNode, float translationY); + private static native boolean nSetTranslationZ(long renderNode, float translationZ); + private static native boolean nSetRotation(long renderNode, float rotation); + private static native boolean nSetRotationX(long renderNode, float rotationX); + private static native boolean nSetRotationY(long renderNode, float rotationY); + private static native boolean nSetScaleX(long renderNode, float scaleX); + private static native boolean nSetScaleY(long renderNode, float scaleY); + private static native boolean nSetStaticMatrix(long renderNode, long nativeMatrix); + private static native boolean nSetAnimationMatrix(long renderNode, long animationMatrix); private static native boolean nHasOverlappingRendering(long renderNode); private static native boolean nGetClipToOutline(long renderNode); diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index c15ce44..79f19b5 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(); @@ -78,8 +78,8 @@ public class SurfaceControl { IBinder displayToken); private static native int nativeGetActiveConfig(IBinder displayToken); private static native boolean nativeSetActiveConfig(IBinder displayToken, int id); - private static native void nativeBlankDisplay(IBinder displayToken); - private static native void nativeUnblankDisplay(IBinder displayToken); + private static native void nativeSetDisplayPowerMode( + IBinder displayToken, int mode); private final CloseGuard mCloseGuard = CloseGuard.get(); @@ -209,6 +209,25 @@ public class SurfaceControl { */ public static final int BUILT_IN_DISPLAY_ID_HDMI = 1; + /* Display power modes * / + + /** + * Display power mode off: used while blanking the screen. + * Use only with {@link SurfaceControl#setDisplayPowerMode()}. + */ + public static final int POWER_MODE_OFF = 0; + + /** + * Display power mode doze: used while putting the screen into low power mode. + * Use only with {@link SurfaceControl#setDisplayPowerMode()}. + */ + public static final int POWER_MODE_DOZE = 1; + + /** + * Display power mode normal: used while unblanking the screen. + * Use only with {@link SurfaceControl#setDisplayPowerMode()}. + */ + public static final int POWER_MODE_NORMAL = 2; /** @@ -487,18 +506,11 @@ public class SurfaceControl { } } - public static void unblankDisplay(IBinder displayToken) { - if (displayToken == null) { - throw new IllegalArgumentException("displayToken must not be null"); - } - nativeUnblankDisplay(displayToken); - } - - public static void blankDisplay(IBinder displayToken) { + public static void setDisplayPowerMode(IBinder displayToken, int mode) { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); } - nativeBlankDisplay(displayToken); + nativeSetDisplayPowerMode(displayToken, mode); } public static SurfaceControl.PhysicalDisplayInfo[] getDisplayConfigs(IBinder displayToken) { @@ -597,8 +609,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 +625,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 +635,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 +646,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 +663,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 +688,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 +700,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 72b9d3e..7bbe84e 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -54,8 +54,6 @@ import java.io.PrintWriter; public class ThreadedRenderer extends HardwareRenderer { private static final String LOGTAG = "ThreadedRenderer"; - private static final Rect NULL_RECT = new Rect(); - // Keep in sync with DrawFrameTask.h SYNC_* flags // Nothing interesting to report private static final int SYNC_OK = 0x0; @@ -74,8 +72,7 @@ public class ThreadedRenderer extends HardwareRenderer { private boolean mProfilingEnabled; ThreadedRenderer(boolean translucent) { - // Temporarily disabled - //AtlasInitializer.sInstance.init(); + AtlasInitializer.sInstance.init(); long rootNodePtr = nCreateRootRenderNode(); mRootNode = RenderNode.adopt(rootNodePtr); @@ -229,7 +226,7 @@ public class ThreadedRenderer extends HardwareRenderer { } @Override - void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty) { + void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks) { attachInfo.mIgnoreDirtyState = true; long frameTimeNanos = mChoreographer.getFrameTimeNanos(); attachInfo.mDrawingTime = frameTimeNanos / TimeUtils.NANOS_PER_MS; @@ -247,12 +244,8 @@ public class ThreadedRenderer extends HardwareRenderer { attachInfo.mIgnoreDirtyState = false; - if (dirty == null) { - dirty = NULL_RECT; - } int syncResult = nSyncAndDrawFrame(mNativeProxy, frameTimeNanos, - recordDuration, view.getResources().getDisplayMetrics().density, - dirty.left, dirty.top, dirty.right, dirty.bottom); + recordDuration, view.getResources().getDisplayMetrics().density); if ((syncResult & SYNC_INVALIDATE_REQUIRED) != 0) { attachInfo.mViewRootImpl.invalidate(); } @@ -332,6 +325,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(); @@ -368,6 +369,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(); @@ -384,8 +387,7 @@ public class ThreadedRenderer extends HardwareRenderer { float lightX, float lightY, float lightZ, float lightRadius); private static native void nSetOpaque(long nativeProxy, boolean opaque); private static native int nSyncAndDrawFrame(long nativeProxy, - long frameTimeNanos, long recordDuration, float density, - int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom); + long frameTimeNanos, long recordDuration, float density); private static native void nRunWithGlContext(long nativeProxy, Runnable runnable); private static native void nDestroyCanvasAndSurface(long nativeProxy); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 117fe8e..434f853 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -431,7 +431,7 @@ import java.util.concurrent.atomic.AtomicInteger; * child. The child must use this size, and guarantee that all of its * descendants will fit within this size. * <li>AT_MOST: This is used by the parent to impose a maximum size on the - * child. The child must gurantee that it and all of its descendants will fit + * child. The child must guarantee that it and all of its descendants will fit * within this size. * </ul> * </p> @@ -5377,8 +5377,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * Gets the location of this view in screen coordintates. * * @param outRect The output location + * @hide */ - void getBoundsOnScreen(Rect outRect) { + public void getBoundsOnScreen(Rect outRect) { if (mAttachInfo == null) { return; } @@ -7648,7 +7649,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * notification is at at most once every * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()} * to avoid unnecessary load to the system. Also once a view has a pending - * notifucation this method is a NOP until the notification has been sent. + * notification this method is a NOP until the notification has been sent. * * @hide */ @@ -9236,6 +9237,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 @@ -9646,7 +9671,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * The transform matrix of this view, which is calculated based on the current - * roation, scale, and pivot properties. + * rotation, scale, and pivot properties. * * @see #getRotation() * @see #getScaleX() @@ -13556,12 +13581,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); @@ -19761,6 +19780,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/ViewGroup.java b/core/java/android/view/ViewGroup.java index 0f40ee7..2905851 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -4530,6 +4530,28 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * Native-calculated damage path + * Returns false if this path was unable to complete successfully. This means + * it hit a ViewParent it doesn't recognize and needs to fall back to calculating + * damage area + * @hide + */ + public boolean damageChildDeferred(View child) { + ViewParent parent = getParent(); + while (parent != null) { + if (parent instanceof ViewGroup) { + parent = parent.getParent(); + } else if (parent instanceof ViewRootImpl) { + ((ViewRootImpl) parent).invalidate(); + return true; + } else { + parent = null; + } + } + return false; + } + + /** * Quick invalidation method called by View.invalidateViewProperty. This doesn't set the * DRAWN flags and doesn't handle the Animation logic that the default invalidation methods * do; all we want to do here is schedule a traversal with the appropriate dirty rect. @@ -4537,6 +4559,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @hide */ public void damageChild(View child, final Rect dirty) { + if (damageChildDeferred(child)) { + return; + } + ViewParent parent = this; final AttachInfo attachInfo = mAttachInfo; @@ -6913,13 +6939,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (getClass() != another.getClass()) { return 1; } - // First is above second. - if (mLocation.bottom - another.mLocation.top <= 0) { - return -1; - } - // First is below second. - if (mLocation.top - another.mLocation.bottom >= 0) { - return 1; + final int topDiference = mLocation.top - another.mLocation.top; + if (topDiference != 0) { + return topDiference; } // LTR if (mLayoutDirection == LAYOUT_DIRECTION_LTR) { @@ -6935,11 +6957,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return -rightDifference; } } - // Break tie by top. - final int topDiference = mLocation.top - another.mLocation.top; - if (topDiference != 0) { - return topDiference; - } // Break tie by height. final int heightDiference = mLocation.height() - another.mLocation.height(); if (heightDiference != 0) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index aa06d15..76d5038 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -181,7 +181,6 @@ public final class ViewRootImpl implements ViewParent, int mWidth; int mHeight; Rect mDirty; - final Rect mCurrentDirty = new Rect(); boolean mIsAnimating; CompatibilityInfo.Translator mTranslator; @@ -230,6 +229,7 @@ public final class ViewRootImpl implements ViewParent, QueuedInputEvent mPendingInputEventTail; int mPendingInputEventCount; boolean mProcessInputEventsScheduled; + boolean mUnbufferedInputDispatch; String mPendingInputEventQueueLengthCounterName = "pq"; InputStage mFirstInputStage; @@ -715,17 +715,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); } @@ -871,7 +860,9 @@ public final class ViewRootImpl implements ViewParent, void invalidate() { mDirty.set(0, 0, mWidth, mHeight); - scheduleTraversals(); + if (!mWillDrawSoon) { + scheduleTraversals(); + } } void invalidateWorld(View view) { @@ -1016,7 +1007,9 @@ public final class ViewRootImpl implements ViewParent, mTraversalBarrier = mHandler.getLooper().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); - scheduleConsumeBatchedInput(); + if (!mUnbufferedInputDispatch) { + scheduleConsumeBatchedInput(); + } notifyRendererOfFramePending(); } } @@ -2444,12 +2437,10 @@ public final class ViewRootImpl implements ViewParent, mHardwareYOffset = yoff; mResizeAlpha = resizeAlpha; - mCurrentDirty.set(dirty); dirty.setEmpty(); mBlockResizeBuffer = false; - attachInfo.mHardwareRenderer.draw(mView, attachInfo, this, - animating ? null : mCurrentDirty); + attachInfo.mHardwareRenderer.draw(mView, attachInfo, this); } else { // If we get here with a disabled & requested hardware renderer, something went // wrong (an invalidate posted right before we destroyed the hardware surface @@ -2616,7 +2607,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 +3889,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 +4013,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 +5286,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 +5698,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 +5738,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 +5784,11 @@ public final class ViewRootImpl implements ViewParent, @Override public void onBatchedInputEventPending() { - scheduleConsumeBatchedInput(); + if (mUnbufferedInputDispatch) { + super.onBatchedInputEventPending(); + } else { + scheduleConsumeBatchedInput(); + } } @Override @@ -5784,6 +5809,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/Window.java b/core/java/android/view/Window.java index ecc4586..0120875 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -1516,6 +1516,29 @@ public abstract class Window { public boolean getAllowExitTransitionOverlap() { return true; } /** + * Returns the duration, in milliseconds, of the window background fade + * when transitioning into or away from an Activity when called with an Activity Transition. + * <p>When executing the enter transition, the background starts transparent + * and fades in. This requires {@link #FEATURE_CONTENT_TRANSITIONS}. The default is + * 300 milliseconds.</p> + * @return The duration of the window background fade to opaque during enter transition. + * @see #getEnterTransition() + */ + public long getTransitionBackgroundFadeDuration() { return 0; } + + /** + * Sets the duration, in milliseconds, of the window background fade + * when transitioning into or away from an Activity when called with an Activity Transition. + * <p>When executing the enter transition, the background starts transparent + * and fades in. This requires {@link #FEATURE_CONTENT_TRANSITIONS}. The default is + * 300 milliseconds.</p> + * @param fadeDurationMillis The duration of the window background fade to or from opaque + * during enter transition. + * @see #setEnterTransition(android.transition.Transition) + */ + public void setTransitionBackgroundFadeDuration(long fadeDurationMillis) { } + + /** * @return the color of the status bar. */ public abstract int getStatusBarColor(); diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 294f472..57e774e 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -87,7 +87,12 @@ public final class WindowInsets { if (mTempRect == null) { mTempRect = new Rect(); } - mTempRect.set(mSystemWindowInsets); + if (mSystemWindowInsets != null) { + mTempRect.set(mSystemWindowInsets); + } else { + // If there were no system window insets, this is just empty. + mTempRect.setEmpty(); + } return mTempRect; } diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index cccfa78..e1f40b7 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -195,6 +195,7 @@ public class BaseInputConnection implements InputConnection { public boolean commitText(CharSequence text, int newCursorPosition) { if (DEBUG) Log.v(TAG, "commitText " + text); replaceText(text, newCursorPosition, false); + mIMM.notifyUserAction(); sendCurrentText(); return true; } @@ -435,6 +436,7 @@ public class BaseInputConnection implements InputConnection { public boolean setComposingText(CharSequence text, int newCursorPosition) { if (DEBUG) Log.v(TAG, "setComposingText " + text); replaceText(text, newCursorPosition, true); + mIMM.notifyUserAction(); return true; } @@ -518,6 +520,7 @@ public class BaseInputConnection implements InputConnection { viewRootImpl.dispatchKeyFromIme(event); } } + mIMM.notifyUserAction(); return false; } @@ -601,10 +604,6 @@ public class BaseInputConnection implements InputConnection { } beginBatchEdit(); - if (!composing && !TextUtils.isEmpty(text)) { - // Notify the text is committed by the user to InputMethodManagerService - mIMM.notifyTextCommitted(); - } // delete composing text set previously. int a = getComposingSpanStart(content); diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index f874eb7..ace8808 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -320,6 +320,25 @@ public final class InputMethodManager { int mCursorCandEnd; /** + * Represents an invalid action notification sequence number. {@link InputMethodManagerService} + * always issues a positive integer for action notification sequence numbers. Thus -1 is + * guaranteed to be different from any valid sequence number. + */ + private static final int NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER = -1; + /** + * The next sequence number that is to be sent to {@link InputMethodManagerService} via + * {@link IInputMethodManager#notifyUserAction(int)} at once when a user action is observed. + */ + private int mNextUserActionNotificationSequenceNumber = + NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER; + + /** + * The last sequence number that is already sent to {@link InputMethodManagerService}. + */ + private int mLastSentUserActionNotificationSequenceNumber = + NOT_AN_ACTION_NOTIFICATION_SEQUENCE_NUMBER; + + /** * The instance that has previously been sent to the input method. */ private CursorAnchorInfo mCursorAnchorInfo = null; @@ -363,7 +382,8 @@ public final class InputMethodManager { static final int MSG_SEND_INPUT_EVENT = 5; static final int MSG_TIMEOUT_INPUT_EVENT = 6; static final int MSG_FLUSH_INPUT_EVENT = 7; - static final int SET_CURSOR_ANCHOR_MONITOR_MODE = 8; + static final int MSG_SET_CURSOR_ANCHOR_MONITOR_MODE = 8; + static final int MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 9; class H extends Handler { H(Looper looper) { @@ -494,7 +514,7 @@ public final class InputMethodManager { finishedInputEvent(msg.arg1, false, false); return; } - case SET_CURSOR_ANCHOR_MONITOR_MODE: { + case MSG_SET_CURSOR_ANCHOR_MONITOR_MODE: { synchronized (mH) { mCursorAnchorMonitorMode = msg.arg1; // Clear the cache. @@ -503,6 +523,11 @@ public final class InputMethodManager { } return; } + case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: { + synchronized (mH) { + mNextUserActionNotificationSequenceNumber = msg.arg1; + } + } } } } @@ -570,7 +595,13 @@ public final class InputMethodManager { @Override public void setCursorAnchorMonitorMode(int monitorMode) { - mH.sendMessage(mH.obtainMessage(SET_CURSOR_ANCHOR_MONITOR_MODE, monitorMode, 0)); + mH.sendMessage(mH.obtainMessage(MSG_SET_CURSOR_ANCHOR_MONITOR_MODE, monitorMode, 0)); + } + + @Override + public void setUserActionNotificationSequenceNumber(int sequenceNumber) { + mH.sendMessage(mH.obtainMessage(MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER, + sequenceNumber, 0)); } }; @@ -1214,6 +1245,8 @@ public final class InputMethodManager { mBindSequence = res.sequence; mCurMethod = res.method; mCurId = res.id; + mNextUserActionNotificationSequenceNumber = + res.userActionNotificationSequenceNumber; } else { if (res.channel != null && res.channel != mCurChannel) { res.channel.dispose(); @@ -1913,13 +1946,33 @@ public final class InputMethodManager { } /** - * Notify the current IME commits text + * Notify that a user took some action with this input method. * @hide */ - public void notifyTextCommitted() { + public void notifyUserAction() { synchronized (mH) { + if (mLastSentUserActionNotificationSequenceNumber == + mNextUserActionNotificationSequenceNumber) { + if (DEBUG) { + Log.w(TAG, "Ignoring notifyUserAction as it has already been sent." + + " mLastSentUserActionNotificationSequenceNumber: " + + mLastSentUserActionNotificationSequenceNumber + + " mNextUserActionNotificationSequenceNumber: " + + mNextUserActionNotificationSequenceNumber); + } + return; + } try { - mService.notifyTextCommitted(); + if (DEBUG) { + Log.w(TAG, "notifyUserAction: " + + " mLastSentUserActionNotificationSequenceNumber: " + + mLastSentUserActionNotificationSequenceNumber + + " mNextUserActionNotificationSequenceNumber: " + + mNextUserActionNotificationSequenceNumber); + } + mService.notifyUserAction(mNextUserActionNotificationSequenceNumber); + mLastSentUserActionNotificationSequenceNumber = + mNextUserActionNotificationSequenceNumber; } catch (RemoteException e) { Log.w(TAG, "IME died: " + mCurId, e); } @@ -2103,6 +2156,10 @@ public final class InputMethodManager { + " mCursorSelEnd=" + mCursorSelEnd + " mCursorCandStart=" + mCursorCandStart + " mCursorCandEnd=" + mCursorCandEnd); + p.println(" mNextUserActionNotificationSequenceNumber=" + + mNextUserActionNotificationSequenceNumber + + " mLastSentUserActionNotificationSequenceNumber=" + + mLastSentUserActionNotificationSequenceNumber); } /** 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/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index 2b75d83..abed082 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -70,8 +70,7 @@ public class CookieManager { /** * Sets a cookie for the given URL. Any existing cookie with the same host, * path and name will be replaced with the new cookie. The cookie being set - * must not have expired and must not be a session cookie, otherwise it - * will be ignored. + * will be ignored if it is expired. * * @param url the URL for which the cookie is set * @param value the cookie as a string, using the format of the 'Set-Cookie' diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index d630a9a..ec396aa 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -92,7 +92,7 @@ public class WebChromeClient { @Deprecated public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) {}; - + /** * Notify the host application that the current page would * like to hide its custom view. @@ -392,6 +392,79 @@ public class WebChromeClient { } /** + * Tell the client to show a file chooser. + * + * This is called to handle HTML forms with 'file' input type, in response to the + * user pressing the "Select File" button. + * To cancel the request, call <code>filePathCallback.onReceiveValue(null)</code> and + * return true. + * + * @param webView The WebView instance that is initiating the request. + * @param filePathCallback Invoke this callback to supply the list of paths to files to upload, + * or NULL to cancel. Must only be called if the + * <code>showFileChooser</code> implementations returns true. + * @param fileChooserParams Describes the mode of file chooser to be opened, and options to be + * used with it. + * @return true if filePathCallback will be invoked, false to use default handling. + * + * @see FileChooserParams + * @hide For API approval + */ + public boolean showFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, + FileChooserParams fileChooserParams) { + return false; + } + + /** + * Parameters used in the {@link #showFileChooser(WebView,ValueCallback<String[]>,FileChooserParams)} + * method. + * + * This is intended to be used as a read-only data struct by the application. + * @hide For API approval + */ + public static class FileChooserParams { + // Flags for mode + /** Bitflag for <code>mode</code> indicating multiple files maybe selected */ + public static final int MODE_OPEN_MULTIPLE = 1 << 0; + /** Bitflag for <code>mode</code> indicating a folder maybe selected. + * The implementation should enumerate all files selected by this operation */ + public static final int MODE_OPEN_FOLDER = 1 << 1; + /** Bitflag for <code>mode</code> indicating a non-existant filename maybe returned */ + public static final int MODE_SAVE = 1 << 2; + + /** + * Bit-field of the <code>MODE_</code> flags. + * + * 0 indicates plain single file open. + */ + public int mode; + + /** + * Comma-seperated list of acceptable MIME types. + */ + public String acceptTypes; + + /** + * true indicates a preference for a live media captured value (e.g. Camera, Microphone). + * + * Use <code>acceptTypes</code> to determine suitable capture devices. + */ + public boolean capture; + + /** + * The title to use for this file selector, or null. + * + * Maybe null, in which case a default title should be used. + */ + public String title; + + /** + * Name of a default selection if appropriate, or null. + */ + public String defaultFilename; + }; + + /** * Tell the client to open a file chooser. * @param uploadFile A ValueCallback to set the URI of the file to upload. * onReceiveValue must be called to wake up the thread.a @@ -399,8 +472,11 @@ public class WebChromeClient { * associated with this file picker. * @param capture The value of the 'capture' attribute of the input tag * associated with this file picker. - * @hide + * + * @deprecated Use {@link #showFileChooser} instead. + * @hide This method was not published in any SDK version. */ + @Deprecated public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) { uploadFile.onReceiveValue(null); } diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index 7c32c5b..d14c19b 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -1460,4 +1460,36 @@ public abstract class WebSettings { * {@link #MIXED_CONTENT_NEVER_ALLOW} or {@link #MIXED_CONTENT_COMPATIBILITY_MODE}. */ public abstract int getMixedContentMode(); + + /** + * Sets whether to use a video overlay for embedded encrypted video. + * In API levels prior to {@link android.os.Build.VERSION_CODES#L}, encrypted video can + * only be rendered directly on a secure video surface, so it had been a hard problem to play + * encrypted video in HTML. When this flag is on, WebView can play encrypted video (MSE/EME) + * by using a video overlay (aka hole-punching) for videos embedded using HTML <video> + * tag.<br> + * Caution: This setting is intended for use only in a narrow set of circumstances and apps + * should only enable it if they require playback of encrypted video content. It will impose + * the following limitations on the WebView: + * <ul> + * <li> Only one video overlay can be played at a time. + * <li> Changes made to position or dimensions of a video element may be propagated to the + * corresponding video overlay with a noticeable delay. + * <li> The video overlay is not visible to web APIs and as such may not interact with + * script or styling. For example, CSS styles applied to the <video> tag may be ignored. + * </ul> + * This is not an exhaustive set of constraints and it may vary with new versions of the + * WebView. + * @hide + */ + public abstract void setVideoOverlayForEmbeddedEncryptedVideoEnabled(boolean flag); + + /** + * Gets whether a video overlay will be used for embedded encrypted video. + * + * @return true if WebView uses a video overlay for embedded encrypted video. + * @see #setVideoOverlayForEmbeddedEncryptedVideoEnabled + * @hide + */ + public abstract boolean getVideoOverlayForEmbeddedEncryptedVideoEnabled(); } diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java index 945e0e3..6e6a987 100644 --- a/core/java/android/webkit/WebViewFactoryProvider.java +++ b/core/java/android/webkit/WebViewFactoryProvider.java @@ -37,13 +37,6 @@ public interface WebViewFactoryProvider { String findAddress(String addr); /** - * Implements the API methods: - * {@link android.webkit.WebView#enablePlatformNotifications()} - * {@link android.webkit.WebView#disablePlatformNotifications()} - */ - void setPlatformNotificationsEnabled(boolean enable); - - /** * Implements the API method: * {@link android.webkit.WebSettings#getDefaultUserAgent(Context) } */ 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); } /** diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl index 03d3b22..77f0dec 100644 --- a/core/java/com/android/internal/app/IMediaContainerService.aidl +++ b/core/java/com/android/internal/app/IMediaContainerService.aidl @@ -25,16 +25,18 @@ import android.content.res.ObbInfo; interface IMediaContainerService { String copyResourceToContainer(in Uri packageURI, String containerId, String key, String resFileName, String publicResFileName, boolean isExternal, - boolean isForwardLocked); + boolean isForwardLocked, in String abiOverride); int copyResource(in Uri packageURI, in ContainerEncryptionParams encryptionParams, in ParcelFileDescriptor outStream); - PackageInfoLite getMinimalPackageInfo(in String packagePath, in int flags, in long threshold); + PackageInfoLite getMinimalPackageInfo(in String packagePath, in int flags, in long threshold, + in String abiOverride); boolean checkInternalFreeStorage(in Uri fileUri, boolean isForwardLocked, in long threshold); - boolean checkExternalFreeStorage(in Uri fileUri, boolean isForwardLocked); + boolean checkExternalFreeStorage(in Uri fileUri, boolean isForwardLocked, in String abiOverride); ObbInfo getObbInfo(in String filename); long calculateDirectorySize(in String directory); /** Return file system stats: [0] is total bytes, [1] is available bytes */ long[] getFileSystemStats(in String path); void clearDirectory(in String directory); - long calculateInstalledSize(in String packagePath, boolean isForwardLocked); + long calculateInstalledSize(in String packagePath, boolean isForwardLocked, + in String abiOverride); } diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 47ef65a..01e5d40 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -35,8 +35,8 @@ import java.util.Set; /* - * This is used in conjunction with DevicePolicyManager.setForwardingIntents to enable intents to be - * passed in and out of a managed profile. + * This is used in conjunction with the {@link setCrossProfileIntentFilter} method of + * {@link DevicePolicyManager} to enable intents to be passed in and out of a managed profile. */ public class IntentForwarderActivity extends Activity { diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 591267e..183dd05 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -484,8 +484,7 @@ public class ResolverActivity extends AlertActivity implements AdapterView.OnIte mList.clear(); if (mBaseResolveList != null) { - currentResolveList = mBaseResolveList; - mOrigResolveList = null; + currentResolveList = mOrigResolveList = mBaseResolveList; } else { currentResolveList = mOrigResolveList = mPm.queryIntentActivities( mIntent, PackageManager.MATCH_DEFAULT_ONLY diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl index 1e37fd9..d10451b 100644 --- a/core/java/com/android/internal/backup/IBackupTransport.aidl +++ b/core/java/com/android/internal/backup/IBackupTransport.aidl @@ -178,7 +178,7 @@ interface IBackupTransport { /** * 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 #nextRestorePackage}. + * @return the same error codes as {@link #startRestore}. */ int getRestoreData(in ParcelFileDescriptor outFd); diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java index 446ef55..7292116 100644 --- a/core/java/com/android/internal/backup/LocalTransport.java +++ b/core/java/com/android/internal/backup/LocalTransport.java @@ -18,6 +18,7 @@ package com.android.internal.backup; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; +import android.app.backup.BackupTransport; import android.app.backup.RestoreSet; import android.content.ComponentName; import android.content.Context; @@ -47,7 +48,7 @@ import static android.system.OsConstants.*; * later restoring from there. For testing only. */ -public class LocalTransport extends IBackupTransport.Stub { +public class LocalTransport extends BackupTransport { private static final String TAG = "LocalTransport"; private static final boolean DEBUG = true; @@ -103,7 +104,7 @@ public class LocalTransport extends IBackupTransport.Stub { public int initializeDevice() { if (DEBUG) Log.v(TAG, "wiping all data"); deleteContents(mCurrentSetDir); - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) { @@ -165,7 +166,7 @@ public class LocalTransport extends IBackupTransport.Stub { entity.write(buf, 0, dataSize); } catch (IOException e) { Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath()); - return BackupConstants.TRANSPORT_ERROR; + return BackupTransport.TRANSPORT_ERROR; } finally { entity.close(); } @@ -173,11 +174,11 @@ public class LocalTransport extends IBackupTransport.Stub { entityFile.delete(); } } - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } catch (IOException e) { // oops, something went wrong. abort the operation and return error. Log.v(TAG, "Exception reading backup input:", e); - return BackupConstants.TRANSPORT_ERROR; + return BackupTransport.TRANSPORT_ERROR; } } @@ -207,17 +208,17 @@ public class LocalTransport extends IBackupTransport.Stub { } packageDir.delete(); } - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } public int finishBackup() { if (DEBUG) Log.v(TAG, "finishBackup()"); - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } // Restore handling static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 }; - public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException { + public RestoreSet[] getAvailableRestoreSets() { long[] existing = new long[POSSIBLE_SETS.length + 1]; int num = 0; @@ -248,7 +249,7 @@ public class LocalTransport extends IBackupTransport.Stub { mRestorePackage = -1; mRestoreToken = token; mRestoreDataDir = new File(mDataDir, Long.toString(token)); - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } public String nextRestorePackage() { @@ -280,7 +281,7 @@ public class LocalTransport extends IBackupTransport.Stub { ArrayList<DecodedFilename> blobs = contentsByKey(packageDir); if (blobs == null) { // nextRestorePackage() ensures the dir exists, so this is an error Log.e(TAG, "No keys for package: " + packageDir); - return BackupConstants.TRANSPORT_ERROR; + return BackupTransport.TRANSPORT_ERROR; } // We expect at least some data if the directory exists in the first place @@ -301,10 +302,10 @@ public class LocalTransport extends IBackupTransport.Stub { in.close(); } } - return BackupConstants.TRANSPORT_OK; + return BackupTransport.TRANSPORT_OK; } catch (IOException e) { Log.e(TAG, "Unable to read backup records", e); - return BackupConstants.TRANSPORT_ERROR; + return BackupTransport.TRANSPORT_ERROR; } } diff --git a/core/java/com/android/internal/backup/LocalTransportService.java b/core/java/com/android/internal/backup/LocalTransportService.java index d05699a..77ac313 100644 --- a/core/java/com/android/internal/backup/LocalTransportService.java +++ b/core/java/com/android/internal/backup/LocalTransportService.java @@ -32,6 +32,6 @@ public class LocalTransportService extends Service { @Override public IBinder onBind(Intent intent) { - return sTransport; + return sTransport.getBinder(); } } diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java index ba419f9..dab3aff 100644 --- a/core/java/com/android/internal/content/NativeLibraryHelper.java +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -20,6 +20,7 @@ import android.content.pm.PackageManager; import android.util.Slog; import java.io.File; +import java.io.IOException; /** * Native libraries helper. @@ -141,4 +142,18 @@ public class NativeLibraryHelper { return deletedFiles; } + + // We don't care about the other return values for now. + private static final int BITCODE_PRESENT = 1; + + public static boolean hasRenderscriptBitcode(ApkHandle handle) throws IOException { + final int returnVal = hasRenderscriptBitcode(handle.apkHandle); + if (returnVal < 0) { + throw new IOException("Error scanning APK, code: " + returnVal); + } + + return (returnVal == BITCODE_PRESENT); + } + + private static native int hasRenderscriptBitcode(long apkHandle); } diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java index 495d5c6..fdd24a6 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java @@ -19,6 +19,7 @@ package com.android.internal.inputmethod; import android.content.Context; import android.content.pm.PackageManager; import android.text.TextUtils; +import android.util.Log; import android.util.Slog; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; @@ -33,6 +34,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.TreeMap; /** @@ -116,6 +118,24 @@ public class InputMethodSubtypeSwitchingController { + " mIsSystemLanguage=" + mIsSystemLanguage + "}"; } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof ImeSubtypeListItem) { + final ImeSubtypeListItem that = (ImeSubtypeListItem)o; + if (!Objects.equals(this.mImi, that.mImi)) { + return false; + } + if (this.mSubtypeId != that.mSubtypeId) { + return false; + } + return true; + } + return false; + } } private static class InputMethodAndSubtypeList { @@ -211,54 +231,233 @@ public class InputMethodSubtypeSwitchingController { } } - private final InputMethodSettings mSettings; - private InputMethodAndSubtypeList mSubtypeList; + private static int calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype) { + return subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi, + subtype.hashCode()) : NOT_A_SUBTYPE_ID; + } - @VisibleForTesting - public static ImeSubtypeListItem getNextInputMethodLockedImpl(List<ImeSubtypeListItem> imList, - boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { - if (imi == null) { - return null; + private static class StaticRotationList { + private final List<ImeSubtypeListItem> mImeSubtypeList; + public StaticRotationList(final List<ImeSubtypeListItem> imeSubtypeList) { + mImeSubtypeList = imeSubtypeList; } - if (imList.size() <= 1) { - return null; - } - // Here we have two rotation groups, depending on the returned boolean value of - // {@link InputMethodInfo#supportsSwitchingToNextInputMethod()}. - final boolean expectedValueOfSupportsSwitchingToNextInputMethod = - imi.supportsSwitchingToNextInputMethod(); - final int N = imList.size(); - final int currentSubtypeId = - subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi, - subtype.hashCode()) : NOT_A_SUBTYPE_ID; - for (int i = 0; i < N; ++i) { - final ImeSubtypeListItem isli = imList.get(i); - // Skip until the current IME/subtype is found. - if (!isli.mImi.equals(imi) || isli.mSubtypeId != currentSubtypeId) { - continue; - } - // Found the current IME/subtype. Start searching the next IME/subtype from here. - for (int j = 0; j < N - 1; ++j) { - final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N); - // Skip if the candidate doesn't belong to the expected rotation group. - if (expectedValueOfSupportsSwitchingToNextInputMethod != - candidate.mImi.supportsSwitchingToNextInputMethod()) { - continue; + + /** + * Returns the index of the specified input method and subtype in the given list. + * @param imi The {@link InputMethodInfo} to be searched. + * @param subtype The {@link InputMethodSubtype} to be searched. null if the input method + * does not have a subtype. + * @return The index in the given list. -1 if not found. + */ + private int getIndex(InputMethodInfo imi, InputMethodSubtype subtype) { + final int currentSubtypeId = calculateSubtypeId(imi, subtype); + final int N = mImeSubtypeList.size(); + for (int i = 0; i < N; ++i) { + final ImeSubtypeListItem isli = mImeSubtypeList.get(i); + // Skip until the current IME/subtype is found. + if (imi.equals(isli.mImi) && isli.mSubtypeId == currentSubtypeId) { + return i; } + } + return -1; + } + + public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, + InputMethodInfo imi, InputMethodSubtype subtype) { + if (imi == null) { + return null; + } + if (mImeSubtypeList.size() <= 1) { + return null; + } + final int currentIndex = getIndex(imi, subtype); + if (currentIndex < 0) { + return null; + } + final int N = mImeSubtypeList.size(); + for (int offset = 1; offset < N; ++offset) { + // Start searching the next IME/subtype from the next of the current index. + final int candidateIndex = (currentIndex + offset) % N; + final ImeSubtypeListItem candidate = mImeSubtypeList.get(candidateIndex); // Skip if searching inside the current IME only, but the candidate is not // the current IME. - if (onlyCurrentIme && !candidate.mImi.equals(imi)) { + if (onlyCurrentIme && !imi.equals(candidate.mImi)) { continue; } return candidate; } - // No appropriate IME/subtype is found in the list. Give up. return null; } - // The current IME/subtype is not found in the list. Give up. - return null; } + private static class DynamicRotationList { + private static final String TAG = DynamicRotationList.class.getSimpleName(); + private final List<ImeSubtypeListItem> mImeSubtypeList; + private final int[] mUsageHistoryOfSubtypeListItemIndex; + + private DynamicRotationList(final List<ImeSubtypeListItem> imeSubtypeListItems) { + mImeSubtypeList = imeSubtypeListItems; + mUsageHistoryOfSubtypeListItemIndex = new int[mImeSubtypeList.size()]; + final int N = mImeSubtypeList.size(); + for (int i = 0; i < N; i++) { + mUsageHistoryOfSubtypeListItemIndex[i] = i; + } + } + + /** + * Returns the index of the specified object in + * {@link #mUsageHistoryOfSubtypeListItemIndex}. + * <p>We call the index of {@link #mUsageHistoryOfSubtypeListItemIndex} as "Usage Rank" + * so as not to be confused with the index in {@link #mImeSubtypeList}. + * @return -1 when the specified item doesn't belong to {@link #mImeSubtypeList} actually. + */ + private int getUsageRank(final InputMethodInfo imi, InputMethodSubtype subtype) { + final int currentSubtypeId = calculateSubtypeId(imi, subtype); + final int N = mUsageHistoryOfSubtypeListItemIndex.length; + for (int usageRank = 0; usageRank < N; usageRank++) { + final int subtypeListItemIndex = mUsageHistoryOfSubtypeListItemIndex[usageRank]; + final ImeSubtypeListItem subtypeListItem = + mImeSubtypeList.get(subtypeListItemIndex); + if (subtypeListItem.mImi.equals(imi) && + subtypeListItem.mSubtypeId == currentSubtypeId) { + return usageRank; + } + } + // Not found in the known IME/Subtype list. + return -1; + } + + public void onUserAction(InputMethodInfo imi, InputMethodSubtype subtype) { + final int currentUsageRank = getUsageRank(imi, subtype); + // Do nothing if currentUsageRank == -1 (not found), or currentUsageRank == 0 + if (currentUsageRank <= 0) { + return; + } + final int currentItemIndex = mUsageHistoryOfSubtypeListItemIndex[currentUsageRank]; + System.arraycopy(mUsageHistoryOfSubtypeListItemIndex, 0, + mUsageHistoryOfSubtypeListItemIndex, 1, currentUsageRank); + mUsageHistoryOfSubtypeListItemIndex[0] = currentItemIndex; + } + + public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, + InputMethodInfo imi, InputMethodSubtype subtype) { + int currentUsageRank = getUsageRank(imi, subtype); + if (currentUsageRank < 0) { + if (DEBUG) { + Slog.d(TAG, "IME/subtype is not found: " + imi.getId() + ", " + subtype); + } + return null; + } + final int N = mUsageHistoryOfSubtypeListItemIndex.length; + for (int i = 1; i < N; i++) { + final int subtypeListItemRank = (currentUsageRank + i) % N; + final int subtypeListItemIndex = + mUsageHistoryOfSubtypeListItemIndex[subtypeListItemRank]; + final ImeSubtypeListItem subtypeListItem = + mImeSubtypeList.get(subtypeListItemIndex); + if (onlyCurrentIme && !imi.equals(subtypeListItem.mImi)) { + continue; + } + return subtypeListItem; + } + return null; + } + } + + @VisibleForTesting + public static class ControllerImpl { + private final DynamicRotationList mSwitchingAwareRotationList; + private final StaticRotationList mSwitchingUnawareRotationList; + + public static ControllerImpl createFrom(final ControllerImpl currentInstance, + final List<ImeSubtypeListItem> sortedEnabledItems) { + DynamicRotationList switchingAwareRotationList = null; + { + final List<ImeSubtypeListItem> switchingAwareImeSubtypes = + filterImeSubtypeList(sortedEnabledItems, + true /* supportsSwitchingToNextInputMethod */); + if (currentInstance != null && + currentInstance.mSwitchingAwareRotationList != null && + Objects.equals(currentInstance.mSwitchingAwareRotationList.mImeSubtypeList, + switchingAwareImeSubtypes)) { + // Can reuse the current instance. + switchingAwareRotationList = currentInstance.mSwitchingAwareRotationList; + } + if (switchingAwareRotationList == null) { + switchingAwareRotationList = new DynamicRotationList(switchingAwareImeSubtypes); + } + } + + StaticRotationList switchingUnawareRotationList = null; + { + final List<ImeSubtypeListItem> switchingUnawareImeSubtypes = filterImeSubtypeList( + sortedEnabledItems, false /* supportsSwitchingToNextInputMethod */); + if (currentInstance != null && + currentInstance.mSwitchingUnawareRotationList != null && + Objects.equals( + currentInstance.mSwitchingUnawareRotationList.mImeSubtypeList, + switchingUnawareImeSubtypes)) { + // Can reuse the current instance. + switchingUnawareRotationList = currentInstance.mSwitchingUnawareRotationList; + } + if (switchingUnawareRotationList == null) { + switchingUnawareRotationList = + new StaticRotationList(switchingUnawareImeSubtypes); + } + } + + return new ControllerImpl(switchingAwareRotationList, switchingUnawareRotationList); + } + + private ControllerImpl(final DynamicRotationList switchingAwareRotationList, + final StaticRotationList switchingUnawareRotationList) { + mSwitchingAwareRotationList = switchingAwareRotationList; + mSwitchingUnawareRotationList = switchingUnawareRotationList; + } + + public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi, + InputMethodSubtype subtype) { + if (imi == null) { + return null; + } + if (imi.supportsSwitchingToNextInputMethod()) { + return mSwitchingAwareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi, + subtype); + } else { + return mSwitchingUnawareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi, + subtype); + } + } + + public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) { + if (imi == null) { + return; + } + if (imi.supportsSwitchingToNextInputMethod()) { + mSwitchingAwareRotationList.onUserAction(imi, subtype); + } + } + + private static List<ImeSubtypeListItem> filterImeSubtypeList( + final List<ImeSubtypeListItem> items, + final boolean supportsSwitchingToNextInputMethod) { + final ArrayList<ImeSubtypeListItem> result = new ArrayList<>(); + final int ALL_ITEMS_COUNT = items.size(); + for (int i = 0; i < ALL_ITEMS_COUNT; i++) { + final ImeSubtypeListItem item = items.get(i); + if (item.mImi.supportsSwitchingToNextInputMethod() == + supportsSwitchingToNextInputMethod) { + result.add(item); + } + } + return result; + } + } + + private final InputMethodSettings mSettings; + private InputMethodAndSubtypeList mSubtypeList; + private ControllerImpl mController; + private InputMethodSubtypeSwitchingController(InputMethodSettings settings, Context context) { mSettings = settings; resetCircularListLocked(context); @@ -269,19 +468,31 @@ public class InputMethodSubtypeSwitchingController { return new InputMethodSubtypeSwitchingController(settings, context); } - // TODO: write unit tests for this method and the logic that determines the next subtype - public void onCommitTextLocked(InputMethodInfo imi, InputMethodSubtype subtype) { - // TODO: Implement this. + public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) { + if (mController == null) { + if (DEBUG) { + Log.e(TAG, "mController shouldn't be null."); + } + return; + } + mController.onUserActionLocked(imi, subtype); } public void resetCircularListLocked(Context context) { mSubtypeList = new InputMethodAndSubtypeList(context, mSettings); + mController = ControllerImpl.createFrom(mController, + mSubtypeList.getSortedInputMethodAndSubtypeList()); } - public ImeSubtypeListItem getNextInputMethodLocked( - boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { - return getNextInputMethodLockedImpl(mSubtypeList.getSortedInputMethodAndSubtypeList(), - onlyCurrentIme, imi, subtype); + public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi, + InputMethodSubtype subtype) { + if (mController == null) { + if (DEBUG) { + Log.e(TAG, "mController shouldn't be null."); + } + return null; + } + return mController.getNextInputMethod(onlyCurrentIme, imi, subtype); } public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListLocked(boolean showSubtypes, diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java index 0d00f41..73d3738 100644 --- a/core/java/com/android/internal/net/VpnConfig.java +++ b/core/java/com/android/internal/net/VpnConfig.java @@ -17,8 +17,10 @@ package com.android.internal.net; import android.app.PendingIntent; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.res.Resources; import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; @@ -45,7 +47,10 @@ public class VpnConfig implements Parcelable { public static Intent getIntentForConfirmation() { Intent intent = new Intent(); - intent.setClassName(DIALOGS_PACKAGE, DIALOGS_PACKAGE + ".ConfirmDialog"); + ComponentName componentName = ComponentName.unflattenFromString( + Resources.getSystem().getString( + com.android.internal.R.string.config_customVpnConfirmDialogComponent)); + intent.setClassName(componentName.getPackageName(), componentName.getClassName()); return intent; } diff --git a/core/java/com/android/internal/os/SomeArgs.java b/core/java/com/android/internal/os/SomeArgs.java index 7edf4cc..c977997 100644 --- a/core/java/com/android/internal/os/SomeArgs.java +++ b/core/java/com/android/internal/os/SomeArgs.java @@ -45,6 +45,7 @@ public final class SomeArgs { public Object arg3; public Object arg4; public Object arg5; + public Object arg6; public int argi1; public int argi2; public int argi3; @@ -95,6 +96,7 @@ public final class SomeArgs { arg3 = null; arg4 = null; arg5 = null; + arg6 = null; argi1 = 0; argi2 = 0; argi3 = 0; diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index a56fa36..d66ef83 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -169,6 +169,15 @@ public class ArrayUtils return false; } + public static boolean contains(long[] array, long value) { + for (long element : array) { + if (element == value) { + return true; + } + } + return false; + } + public static long total(long[] array) { long total = 0; for (long value : array) { @@ -229,6 +238,14 @@ public class ArrayUtils return array; } + /** + * Appends a new value to a copy of the array and returns the copy. If + * the value is already present, the original array is returned + * @param cur The original array, or null to represent an empty array. + * @param val The value to add. + * @return A new array that contains all of the values of the original array + * with the new value added, or the original array. + */ public static int[] appendInt(int[] cur, int val) { if (cur == null) { return new int[] { val }; @@ -264,4 +281,48 @@ public class ArrayUtils } return cur; } + + /** + * Appends a new value to a copy of the array and returns the copy. If + * the value is already present, the original array is returned + * @param cur The original array, or null to represent an empty array. + * @param val The value to add. + * @return A new array that contains all of the values of the original array + * with the new value added, or the original array. + */ + public static long[] appendLong(long[] cur, long val) { + if (cur == null) { + return new long[] { val }; + } + final int N = cur.length; + for (int i = 0; i < N; i++) { + if (cur[i] == val) { + return cur; + } + } + long[] ret = new long[N + 1]; + System.arraycopy(cur, 0, ret, 0, N); + ret[N] = val; + return ret; + } + + public static long[] removeLong(long[] cur, long val) { + if (cur == null) { + return null; + } + final int N = cur.length; + for (int i = 0; i < N; i++) { + if (cur[i] == val) { + long[] ret = new long[N - 1]; + if (i > 0) { + System.arraycopy(cur, 0, ret, 0, i); + } + if (i < (N - 1)) { + System.arraycopy(cur, i + 1, ret, i, N - i - 1); + } + return ret; + } + } + return cur; + } } diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl index 9e8d12b..b100d27 100644 --- a/core/java/com/android/internal/view/IInputMethodClient.aidl +++ b/core/java/com/android/internal/view/IInputMethodClient.aidl @@ -28,4 +28,5 @@ oneway interface IInputMethodClient { void onUnbindMethod(int sequence); void setActive(boolean active); void setCursorAnchorMonitorMode(int monitorMode); + void setUserActionNotificationSequenceNumber(int sequenceNumber); } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 5336174..b84c359 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -77,6 +77,6 @@ interface IInputMethodManager { boolean setInputMethodEnabled(String id, boolean enabled); void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes); int getInputMethodWindowVisibleHeight(); - oneway void notifyTextCommitted(); + oneway void notifyUserAction(int sequenceNumber); void setCursorAnchorMonitorMode(in IBinder token, int monitorMode); } diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java index 14afe21..3a3e56d 100644 --- a/core/java/com/android/internal/view/InputBindResult.java +++ b/core/java/com/android/internal/view/InputBindResult.java @@ -47,13 +47,19 @@ public final class InputBindResult implements Parcelable { * Sequence number of this binding. */ public final int sequence; - + + /** + * Sequence number of user action notification. + */ + public final int userActionNotificationSequenceNumber; + public InputBindResult(IInputMethodSession _method, InputChannel _channel, - String _id, int _sequence) { + String _id, int _sequence, int _userActionNotificationSequenceNumber) { method = _method; channel = _channel; id = _id; sequence = _sequence; + userActionNotificationSequenceNumber = _userActionNotificationSequenceNumber; } InputBindResult(Parcel source) { @@ -65,12 +71,15 @@ public final class InputBindResult implements Parcelable { } id = source.readString(); sequence = source.readInt(); + userActionNotificationSequenceNumber = source.readInt(); } @Override public String toString() { return "InputBindResult{" + method + " " + id - + " #" + sequence + "}"; + + " sequence:" + sequence + + " userActionNotificationSequenceNumber:" + userActionNotificationSequenceNumber + + "}"; } /** @@ -90,6 +99,7 @@ public final class InputBindResult implements Parcelable { } dest.writeString(id); dest.writeInt(sequence); + dest.writeInt(userActionNotificationSequenceNumber); } /** diff --git a/core/java/com/android/server/SystemService.java b/core/java/com/android/server/SystemService.java index bf36bb1..43a05d0 100644 --- a/core/java/com/android/server/SystemService.java +++ b/core/java/com/android/server/SystemService.java @@ -193,58 +193,4 @@ public abstract class SystemService { private SystemServiceManager getManager() { return LocalServices.getService(SystemServiceManager.class); } - -// /** -// * Called when a new user has been created. If your service deals with multiple users, this -// * method should be overridden. -// * -// * @param userHandle The user that was created. -// */ -// public void onUserCreated(int userHandle) { -// } -// -// /** -// * Called when an existing user has started a new session. If your service deals with multiple -// * users, this method should be overridden. -// * -// * @param userHandle The user who started a new session. -// */ -// public void onUserStarted(int userHandle) { -// } -// -// /** -// * Called when a background user session has entered the foreground. If your service deals with -// * multiple users, this method should be overridden. -// * -// * @param userHandle The user who's session entered the foreground. -// */ -// public void onUserForeground(int userHandle) { -// } -// -// /** -// * Called when a foreground user session has entered the background. If your service deals with -// * multiple users, this method should be overridden; -// * -// * @param userHandle The user who's session entered the background. -// */ -// public void onUserBackground(int userHandle) { -// } -// -// /** -// * Called when a user's active session has stopped. If your service deals with multiple users, -// * this method should be overridden. -// * -// * @param userHandle The user who's session has stopped. -// */ -// public void onUserStopped(int userHandle) { -// } -// -// /** -// * Called when a user has been removed from the system. If your service deals with multiple -// * users, this method should be overridden. -// * -// * @param userHandle The user who has been removed. -// */ -// public void onUserRemoved(int userHandle) { -// } } |
