diff options
Diffstat (limited to 'core')
121 files changed, 4381 insertions, 1891 deletions
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java index 427ecce..435d5ab 100644 --- a/core/java/android/animation/AnimatorInflater.java +++ b/core/java/android/animation/AnimatorInflater.java @@ -108,7 +108,7 @@ public class AnimatorInflater { float pathErrorScale) throws NotFoundException { final ConfigurationBoundResourceCache<Animator> animatorCache = resources .getAnimatorCache(); - Animator animator = animatorCache.get(id, theme); + Animator animator = animatorCache.getInstance(id, theme); if (animator != null) { if (DBG_ANIMATOR_INFLATER) { Log.d(TAG, "loaded animator from cache, " + resources.getResourceName(id)); @@ -157,7 +157,7 @@ public class AnimatorInflater { final ConfigurationBoundResourceCache<StateListAnimator> cache = resources .getStateListAnimatorCache(); final Theme theme = context.getTheme(); - StateListAnimator animator = cache.get(id, theme); + StateListAnimator animator = cache.getInstance(id, theme); if (animator != null) { return animator; } diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java index 5790682..cdd72be 100644 --- a/core/java/android/animation/LayoutTransition.java +++ b/core/java/android/animation/LayoutTransition.java @@ -28,6 +28,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** * This class enables automatic animations on layout changes in ViewGroup objects. To enable @@ -757,7 +758,7 @@ public class LayoutTransition { // reset the inter-animation delay, in case we use it later staggerDelay = 0; - final ViewTreeObserver observer = parent.getViewTreeObserver(); // used for later cleanup + final ViewTreeObserver observer = parent.getViewTreeObserver(); if (!observer.isAlive()) { // If the observer's not in a good state, skip the transition return; @@ -790,21 +791,9 @@ public class LayoutTransition { // This is the cleanup step. When we get this rendering event, we know that all of // the appropriate animations have been set up and run. Now we can clear out the // layout listeners. - observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - public boolean onPreDraw() { - parent.getViewTreeObserver().removeOnPreDrawListener(this); - int count = layoutChangeListenerMap.size(); - if (count > 0) { - Collection<View> views = layoutChangeListenerMap.keySet(); - for (View view : views) { - View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view); - view.removeOnLayoutChangeListener(listener); - } - } - layoutChangeListenerMap.clear(); - return true; - } - }); + CleanupCallback callback = new CleanupCallback(layoutChangeListenerMap, parent); + observer.addOnPreDrawListener(callback); + parent.addOnAttachStateChangeListener(callback); } /** @@ -1499,4 +1488,50 @@ public class LayoutTransition { View view, int transitionType); } + /** + * Utility class to clean up listeners after animations are setup. Cleanup happens + * when either the OnPreDrawListener method is called or when the parent is detached, + * whichever comes first. + */ + private static final class CleanupCallback implements ViewTreeObserver.OnPreDrawListener, + View.OnAttachStateChangeListener { + + final Map<View, View.OnLayoutChangeListener> layoutChangeListenerMap; + final ViewGroup parent; + + CleanupCallback(Map<View, View.OnLayoutChangeListener> listenerMap, ViewGroup parent) { + this.layoutChangeListenerMap = listenerMap; + this.parent = parent; + } + + private void cleanup() { + parent.getViewTreeObserver().removeOnPreDrawListener(this); + parent.removeOnAttachStateChangeListener(this); + int count = layoutChangeListenerMap.size(); + if (count > 0) { + Collection<View> views = layoutChangeListenerMap.keySet(); + for (View view : views) { + View.OnLayoutChangeListener listener = layoutChangeListenerMap.get(view); + view.removeOnLayoutChangeListener(listener); + } + layoutChangeListenerMap.clear(); + } + } + + @Override + public void onViewAttachedToWindow(View v) { + } + + @Override + public void onViewDetachedFromWindow(View v) { + cleanup(); + } + + @Override + public boolean onPreDraw() { + cleanup(); + return true; + } + }; + } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index afbddd9..9968dbb 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -89,7 +89,7 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; -import android.view.PhoneWindow; +import com.android.internal.policy.PhoneWindow; import android.view.SearchEvent; import android.view.View; import android.view.View.OnCreateContextMenuListener; @@ -6498,6 +6498,11 @@ public class Activity extends ContextThemeWrapper return (w == null) ? 0 : w.getAttributes().windowAnimations; } + @Override + public void onAttachFragment(Fragment fragment) { + Activity.this.onAttachFragment(fragment); + } + @Nullable @Override public View onFindViewById(int id) { diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 576a046..9bbb4be 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -16,8 +16,10 @@ package android.app; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.graphics.Canvas; import android.graphics.Matrix; @@ -26,6 +28,7 @@ import android.os.BatteryStats; import android.os.IBinder; import android.os.ParcelFileDescriptor; +import android.util.Log; import com.android.internal.app.ProcessStats; import com.android.internal.os.TransferPipe; import com.android.internal.util.FastPrintWriter; @@ -2396,7 +2399,24 @@ public class ActivityManager { } catch (RemoteException e) { } } - + + /** + * Kills the specified UID. + * @param uid The UID to kill. + * @param reason The reason for the kill. + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.KILL_UID) + public void killUid(int uid, String reason) { + try { + ActivityManagerNative.getDefault().killUid(uid, reason); + } catch (RemoteException e) { + Log.e(TAG, "Couldn't kill uid:" + uid, e); + } + } + /** * Have the system perform a force stop of everything associated with * the given application package. All processes that share its uid diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index da6d8c5..f16406a 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -38,6 +38,7 @@ import android.content.res.AssetManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDebug; import android.database.sqlite.SQLiteDebug.DbStats; @@ -4186,9 +4187,14 @@ public final class ActivityThread { if (!mConfiguration.isOtherSeqNewer(config) && compat == null) { return; } - configDiff = mConfiguration.diff(config); - mConfiguration.updateFrom(config); + + configDiff = mConfiguration.updateFrom(config); config = applyCompatConfiguration(mCurDefaultDisplayDpi); + + final Theme systemTheme = getSystemContext().getTheme(); + if ((systemTheme.getChangingConfigurations() & configDiff) != 0) { + systemTheme.rebase(); + } } ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config); diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java index 2939322..968c956 100644 --- a/core/java/android/app/ActivityTransitionCoordinator.java +++ b/core/java/android/app/ActivityTransitionCoordinator.java @@ -476,9 +476,8 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { tempRect.set(0, 0, width, height); view.getMatrix().mapRect(tempRect); - ViewGroup parent = (ViewGroup) view.getParent(); - left = leftInParent - tempRect.left + parent.getScrollX(); - top = topInParent - tempRect.top + parent.getScrollY(); + left = leftInParent - tempRect.left; + top = topInParent - tempRect.top; right = left + width; bottom = top + height; } @@ -506,7 +505,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { ViewGroup parent = (ViewGroup) view.getParent(); Matrix matrix = new Matrix(); parent.transformMatrixToLocal(matrix); - + matrix.postTranslate(parent.getScrollX(), parent.getScrollY()); mSharedElementParentMatrices.add(matrix); } } @@ -521,6 +520,7 @@ abstract class ActivityTransitionCoordinator extends ResultReceiver { // Find the location in the view's parent ViewGroup parent = (ViewGroup) viewParent; parent.transformMatrixToLocal(matrix); + matrix.postTranslate(parent.getScrollX(), parent.getScrollY()); } } else { // The indices of mSharedElementParentMatrices matches the diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 6bbbf9e..5aa399b 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -941,7 +941,8 @@ public class AppOpsManager { * @hide */ public static int permissionToOpCode(String permission) { - return sPermToOp.get(permission); + Integer boxedOpCode = sPermToOp.get(permission); + return boxedOpCode != null ? boxedOpCode : OP_NONE; } /** diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java index 75e4bab..1174387 100644 --- a/core/java/android/app/Application.java +++ b/core/java/android/app/Application.java @@ -18,6 +18,7 @@ package android.app; import java.util.ArrayList; +import android.annotation.CallSuper; import android.content.ComponentCallbacks; import android.content.ComponentCallbacks2; import android.content.Context; @@ -89,6 +90,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { * service, or receiver in a process. * If you override this method, be sure to call super.onCreate(). */ + @CallSuper public void onCreate() { } @@ -98,9 +100,11 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { * removed by simply killing them; no user code (including this callback) * is executed when doing so. */ + @CallSuper public void onTerminate() { } + @CallSuper public void onConfigurationChanged(Configuration newConfig) { Object[] callbacks = collectComponentCallbacks(); if (callbacks != null) { @@ -110,6 +114,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { } } + @CallSuper public void onLowMemory() { Object[] callbacks = collectComponentCallbacks(); if (callbacks != null) { @@ -119,6 +124,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 { } } + @CallSuper public void onTrimMemory(int level) { Object[] callbacks = collectComponentCallbacks(); if (callbacks != null) { diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index d049104..f6e0e1e 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -48,7 +48,7 @@ import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; -import android.view.PhoneWindow; +import com.android.internal.policy.PhoneWindow; import android.view.SearchEvent; import android.view.View; import android.view.View.OnCreateContextMenuListener; diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 91d810e..40c5c64 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -1314,6 +1314,12 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene com.android.internal.R.styleable.Fragment_fragmentAllowReturnTransitionOverlap, true); } a.recycle(); + + final Activity hostActivity = mHost == null ? null : mHost.getActivity(); + if (hostActivity != null) { + mCalled = false; + onInflate(hostActivity, attrs, savedInstanceState); + } } /** diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java index dad2c79..3e753f0 100644 --- a/core/java/android/app/FragmentHostCallback.java +++ b/core/java/android/app/FragmentHostCallback.java @@ -23,7 +23,6 @@ import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.util.ArrayMap; -import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; @@ -140,6 +139,14 @@ public abstract class FragmentHostCallback<E> extends FragmentContainer { return mWindowAnimations; } + /** + * Called when a {@link Fragment} is being attached to this host, immediately + * after the call to its {@link Fragment#onAttach(Context)} method and before + * {@link Fragment#onCreate(Bundle)}. + */ + public void onAttachFragment(Fragment fragment) { + } + @Nullable @Override public View onFindViewById(int id) { @@ -187,14 +194,6 @@ public abstract class FragmentHostCallback<E> extends FragmentContainer { } } - void onFragmentInflate(Fragment fragment, AttributeSet attrs, Bundle savedInstanceState) { - fragment.onInflate(mContext, attrs, savedInstanceState); - } - - void onFragmentAttach(Fragment fragment) { - fragment.onAttach(mContext); - } - void doLoaderStart() { if (mLoadersStarted) { return; diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 62436e9..6b5239d 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -844,13 +844,13 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate f.mFragmentManager = mParent != null ? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl(); f.mCalled = false; - mHost.onFragmentAttach(f); + f.onAttach(mHost.getContext()); if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onAttach()"); } if (f.mParentFragment == null) { - mHost.onFragmentAttach(f); + mHost.onAttachFragment(f); } if (!f.mRetaining) { @@ -2107,7 +2107,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate fragment.mTag = tag; fragment.mInLayout = true; fragment.mFragmentManager = this; - mHost.onFragmentInflate(fragment, attrs, fragment.mSavedFragmentState); + fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState); addFragment(fragment, true); } else if (fragment.mInLayout) { // A fragment already exists and it is not one we restored from @@ -2124,7 +2124,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate // from last saved state), then give it the attributes to // initialize itself. if (!fragment.mRetaining) { - mHost.onFragmentInflate(fragment, attrs, fragment.mSavedFragmentState); + fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState); } } diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index b1a5d21..fa11221 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -165,8 +165,6 @@ public class SearchDialog extends Dialog { setContentView(com.android.internal.R.layout.search_bar); // get the view elements for local access - SearchBar searchBar = (SearchBar) findViewById(com.android.internal.R.id.search_bar); - searchBar.setSearchDialog(this); mSearchView = (SearchView) findViewById(com.android.internal.R.id.search_view); mSearchView.setIconified(false); mSearchView.setOnCloseListener(mOnCloseListener); @@ -618,8 +616,6 @@ public class SearchDialog extends Dialog { */ public static class SearchBar extends LinearLayout { - private SearchDialog mSearchDialog; - public SearchBar(Context context, AttributeSet attrs) { super(context, attrs); } @@ -628,18 +624,6 @@ public class SearchDialog extends Dialog { super(context); } - public void setSearchDialog(SearchDialog searchDialog) { - mSearchDialog = searchDialog; - } - - /** - * Don't allow action modes in a SearchBar, it looks silly. - */ - @Override - public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) { - return null; - } - @Override public ActionMode startActionModeForChild( View child, ActionMode.Callback callback, int type) { diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 46da025..391131a 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -91,7 +91,6 @@ import android.os.IPowerManager; import android.os.IUserManager; import android.os.PowerManager; import android.os.Process; -import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemVibrator; import android.os.UserHandle; @@ -111,7 +110,7 @@ import android.telephony.TelephonyManager; import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; -import android.view.PhoneLayoutInflater; +import com.android.internal.policy.PhoneLayoutInflater; import android.view.WindowManager; import android.view.WindowManagerImpl; import android.view.accessibility.AccessibilityManager; diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index aea413d..470804d 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -231,7 +231,7 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * device owner app. * * <p>The broadcast will be limited to the {@link DeviceAdminReceiver} component specified in - * the (@link DevicePolicyManager#EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME) field + * the {@link DevicePolicyManager#EXTRA_PROVISIONING_DEVICE_INITIALIZER_COMPONENT_NAME} field * of the original intent or NFC bump that started the provisioning process. You will generally * handle this in {@link DeviceAdminReceiver#onReadyForUserInitialization}. * @@ -450,9 +450,9 @@ public class DeviceAdminReceiver extends BroadcastReceiver { * * <p>It is not assumed that the device initializer is finished when it returns from * this call, as it may do additional setup asynchronously. The device initializer must call - * {DevicePolicyManager#setUserEnabled(ComponentName admin)} when it has finished any additional - * setup (such as adding an account by using the {@link AccountManager}) in order for the user - * to be functional. + * {@link DevicePolicyManager#setUserEnabled(ComponentName admin)} when it has finished any + * additional setup (such as adding an account by using the {@link AccountManager}) in order for + * the user to be functional. * * @param context The running context as per {@link #onReceive}. * @param intent The received intent as per {@link #onReceive}. diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 9f71ea5..8009b6c 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -412,7 +412,7 @@ public class DevicePolicyManager { = "android.app.action.MANAGED_PROFILE_PROVISIONED"; /** - * A boolean extra indicating whether device encryption is required as part of Device Owner + * A boolean extra indicating whether device encryption can be skipped as part of Device Owner * provisioning. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC_V2} that starts device owner @@ -4294,14 +4294,14 @@ public class DevicePolicyManager { * being disabled. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @param enabled New state of the keyguard. + * @param disabled {@code true} disables the keyguard, {@code false} reenables it. * * @return {@code false} if attempting to disable the keyguard while a lock password was in - * place. {@code true} otherwise." + * place. {@code true} otherwise. */ - public boolean setKeyguardEnabledState(ComponentName admin, boolean enabled) { + public boolean setKeyguardDisabled(ComponentName admin, boolean disabled) { try { - return mService.setKeyguardEnabledState(admin, enabled); + return mService.setKeyguardDisabled(admin, disabled); } catch (RemoteException re) { Log.w(TAG, "Failed talking with device policy service", re); return false; @@ -4309,18 +4309,22 @@ public class DevicePolicyManager { } /** - * Called by device owner to set the enabled state of the status bar. Disabling the status - * bar blocks notifications, quick settings and other screen overlays that allow escaping from + * Called by device owner to disable the status bar. Disabling the status bar blocks + * notifications, quick settings and other screen overlays that allow escaping from * a single use device. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. - * @param enabled New state of the status bar. + * @param disabled {@code true} disables the status bar, {@code false} reenables it. + * + * @return {@code false} if attempting to disable the status bar failed. + * {@code true} otherwise. */ - public void setStatusBarEnabledState(ComponentName admin, boolean enabled) { + public boolean setStatusBarDisabled(ComponentName admin, boolean disabled) { try { - mService.setStatusBarEnabledState(admin, enabled); + return mService.setStatusBarDisabled(admin, disabled); } catch (RemoteException re) { Log.w(TAG, "Failed talking with device policy service", re); + return false; } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 833bc00..e81e7c1 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -224,8 +224,8 @@ interface IDevicePolicyManager { void setSystemUpdatePolicy(in ComponentName who, in PersistableBundle policy); PersistableBundle getSystemUpdatePolicy(); - boolean setKeyguardEnabledState(in ComponentName admin, boolean enabled); - void setStatusBarEnabledState(in ComponentName who, boolean enabled); + boolean setKeyguardDisabled(in ComponentName admin, boolean disabled); + boolean setStatusBarDisabled(in ComponentName who, boolean disabled); boolean getDoNotAskCredentialsOnBoot(); void notifyPendingSystemUpdate(in long updateReceivedTime); diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl index 17cff5c..32951d9 100644 --- a/core/java/android/app/trust/ITrustManager.aidl +++ b/core/java/android/app/trust/ITrustManager.aidl @@ -32,4 +32,5 @@ interface ITrustManager { void reportKeyguardShowingChanged(); boolean isDeviceLocked(int userId); boolean isDeviceSecure(int userId); + boolean hasUserAuthenticatedSinceBoot(int userId); } diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java index b5c5317..8cab565 100644 --- a/core/java/android/app/trust/TrustManager.java +++ b/core/java/android/app/trust/TrustManager.java @@ -147,6 +147,23 @@ public class TrustManager { } } + /** + * Checks whether the specified user has been authenticated since the last boot. + * + * @param userId the user id of the user to check for + * @return true if the user has authenticated since boot, false otherwise + * + * Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission. + */ + public boolean hasUserAuthenticatedSinceBoot(int userId) { + try { + return mService.hasUserAuthenticatedSinceBoot(userId); + } catch (RemoteException e) { + onError(e); + return false; + } + } + private void onError(Exception e) { Log.e(TAG, "Error while calling TrustManagerService", e); } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 54fe786..d0298cd 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -17,6 +17,7 @@ package android.content; import android.content.pm.ApplicationInfo; +import android.os.ResultReceiver; import android.provider.MediaStore; import android.util.ArraySet; @@ -3291,11 +3292,79 @@ public class Intent implements Parcelable, Cloneable { /** * An Intent describing the choices you would like shown with - * {@link #ACTION_PICK_ACTIVITY}. + * {@link #ACTION_PICK_ACTIVITY} or {@link #ACTION_CHOOSER}. */ public static final String EXTRA_INTENT = "android.intent.extra.INTENT"; /** + * An Intent[] describing additional, alternate choices you would like shown with + * {@link #ACTION_CHOOSER}. + * + * <p>An app may be capable of providing several different payload types to complete a + * user's intended action. For example, an app invoking {@link #ACTION_SEND} to share photos + * with another app may use EXTRA_ALTERNATE_INTENTS to have the chooser transparently offer + * several different supported sending mechanisms for sharing, such as the actual "image/*" + * photo data or a hosted link where the photos can be viewed.</p> + * + * <p>The intent present in {@link #EXTRA_INTENT} will be treated as the + * first/primary/preferred intent in the set. Additional intents specified in + * this extra are ordered; by default intents that appear earlier in the array will be + * preferred over intents that appear later in the array as matches for the same + * target component. To alter this preference, a calling app may also supply + * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER}.</p> + */ + public static final String EXTRA_ALTERNATE_INTENTS = "android.intent.extra.ALTERNATE_INTENTS"; + + /** + * An {@link IntentSender} for an Activity that will be invoked when the user makes a selection + * from the chooser activity presented by {@link #ACTION_CHOOSER}. + * + * <p>An app preparing an action for another app to complete may wish to allow the user to + * disambiguate between several options for completing the action based on the chosen target + * or otherwise refine the action before it is invoked. + * </p> + * + * <p>When sent, this IntentSender may be filled in with the following extras:</p> + * <ul> + * <li>{@link #EXTRA_INTENT} The first intent that matched the user's chosen target</li> + * <li>{@link #EXTRA_ALTERNATE_INTENTS} Any additional intents that also matched the user's + * chosen target beyond the first</li> + * <li>{@link #EXTRA_RESULT_RECEIVER} A {@link ResultReceiver} that the refinement activity + * should fill in and send once the disambiguation is complete</li> + * </ul> + */ + public static final String EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER + = "android.intent.extra.CHOOSER_REFINEMENT_INTENT_SENDER"; + + /** + * A {@link ResultReceiver} used to return data back to the sender. + * + * <p>Used to complete an app-specific + * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER refinement} for {@link #ACTION_CHOOSER}.</p> + * + * <p>If {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER} is present in the intent + * used to start a {@link #ACTION_CHOOSER} activity this extra will be + * {@link #fillIn(Intent, int) filled in} to that {@link IntentSender} and sent + * when the user selects a target component from the chooser. It is up to the recipient + * to send a result to this ResultReceiver to signal that disambiguation is complete + * and that the chooser should invoke the user's choice.</p> + * + * <p>The disambiguator should provide a Bundle to the ResultReceiver with an intent + * assigned to the key {@link #EXTRA_INTENT}. This supplied intent will be used by the chooser + * to match and fill in the final Intent or ChooserTarget before starting it. + * The supplied intent must {@link #filterEquals(Intent) match} one of the intents from + * {@link #EXTRA_INTENT} or {@link #EXTRA_ALTERNATE_INTENTS} passed to + * {@link #EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER} to be accepted.</p> + * + * <p>The result code passed to the ResultReceiver should be + * {@link android.app.Activity#RESULT_OK} if the refinement succeeded and the supplied intent's + * target in the chooser should be started, or {@link android.app.Activity#RESULT_CANCELED} if + * the chooser should finish without starting a target.</p> + */ + public static final String EXTRA_RESULT_RECEIVER + = "android.intent.extra.RESULT_RECEIVER"; + + /** * A CharSequence dialog title to provide to the user when used with a * {@link #ACTION_CHOOSER}. */ diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 16f6b1e..43cc63b 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -570,13 +570,16 @@ public class ActivityInfo extends ComponentInfo Configuration.NATIVE_CONFIG_DENSITY, // DENSITY Configuration.NATIVE_CONFIG_LAYOUTDIR, // LAYOUT DIRECTION }; - /** @hide + + /** * Convert Java change bits to native. + * + * @hide */ public static int activityInfoConfigToNative(int input) { int output = 0; - for (int i=0; i<CONFIG_NATIVE_BITS.length; i++) { - if ((input&(1<<i)) != 0) { + for (int i = 0; i < CONFIG_NATIVE_BITS.length; i++) { + if ((input & (1 << i)) != 0) { output |= CONFIG_NATIVE_BITS[i]; } } @@ -584,6 +587,21 @@ public class ActivityInfo extends ComponentInfo } /** + * Convert native change bits to Java. + * + * @hide + */ + public static int activityInfoConfigNativeToJava(int input) { + int output = 0; + for (int i = 0; i < CONFIG_NATIVE_BITS.length; i++) { + if ((input & CONFIG_NATIVE_BITS[i]) != 0) { + output |= (1 << i); + } + } + return output; + } + + /** * @hide * Unfortunately some developers (OpenFeint I am looking at you) have * compared the configChanges bit field against absolute values, so if we diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 0b24594..94b0223 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -96,9 +96,9 @@ interface IPackageManager { void removePermission(String name); - boolean grantPermission(String packageName, String permissionName, int userId); + void grantPermission(String packageName, String permissionName, int userId); - boolean revokePermission(String packageName, String permissionName, int userId); + void revokePermission(String packageName, String permissionName, int userId); boolean isProtectedBroadcast(String actionName); diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index a176593..8d96f5c 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -785,10 +785,12 @@ public final class AssetManager implements AutoCloseable { private native final void deleteTheme(long theme); /*package*/ native static final void applyThemeStyle(long theme, int styleRes, boolean force); /*package*/ native static final void copyTheme(long dest, long source); + /*package*/ native static final void clearTheme(long theme); /*package*/ native static final int loadThemeAttributeValue(long theme, int ident, TypedValue outValue, boolean resolve); /*package*/ native static final void dumpTheme(long theme, int priority, String tag, String prefix); + /*package*/ native static final int getThemeChangingConfigurations(long theme); private native final long openXmlAssetNative(int cookie, String fileName); diff --git a/core/java/android/content/res/ConfigurationBoundResourceCache.java b/core/java/android/content/res/ConfigurationBoundResourceCache.java index cde7e84..fecda87 100644 --- a/core/java/android/content/res/ConfigurationBoundResourceCache.java +++ b/core/java/android/content/res/ConfigurationBoundResourceCache.java @@ -1,138 +1,58 @@ /* -* 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.res; + * 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. + */ -import android.util.ArrayMap; -import android.util.LongSparseArray; -import java.lang.ref.WeakReference; +package android.content.res; /** * A Cache class which can be used to cache resource objects that are easy to clone but more * expensive to inflate. - * @hide + * + * @hide For internal use only. */ -public class ConfigurationBoundResourceCache<T> { - - private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState<T>>>> mCache = - new ArrayMap<String, LongSparseArray<WeakReference<ConstantState<T>>>>(); - - final Resources mResources; +public class ConfigurationBoundResourceCache<T> extends ThemedResourceCache<ConstantState<T>> { + private final Resources mResources; /** - * Creates a Resource cache for the given Resources instance. + * Creates a cache for the given Resources instance. * - * @param resources The Resource which can be used when creating new instances. + * @param resources the resources to use when creating new instances */ public ConfigurationBoundResourceCache(Resources resources) { mResources = resources; } /** - * Adds a new item to the cache. - * - * @param key A custom key that uniquely identifies the resource. - * @param theme The Theme instance where this resource was loaded. - * @param constantState The constant state that can create new instances of the resource. + * If the resource is cached, creates and returns a new instance of it. * + * @param key a key that uniquely identifies the drawable resource + * @param theme the theme where the resource will be used + * @return a new instance of the resource, or {@code null} if not in + * the cache */ - public void put(long key, Resources.Theme theme, ConstantState<T> constantState) { - if (constantState == null) { - return; - } - final String themeKey = theme == null ? "" : theme.getKey(); - LongSparseArray<WeakReference<ConstantState<T>>> themedCache; - synchronized (this) { - themedCache = mCache.get(themeKey); - if (themedCache == null) { - themedCache = new LongSparseArray<WeakReference<ConstantState<T>>>(1); - mCache.put(themeKey, themedCache); - } - themedCache.put(key, new WeakReference<ConstantState<T>>(constantState)); - } - } - - /** - * If the resource is cached, creates a new instance of it and returns. - * - * @param key The long key which can be used to uniquely identify the resource. - * @param theme The The Theme instance where we want to load this resource. - * - * @return If this resources was loaded before, returns a new instance of it. Otherwise, returns - * null. - */ - public T get(long key, Resources.Theme theme) { - final String themeKey = theme != null ? theme.getKey() : ""; - final LongSparseArray<WeakReference<ConstantState<T>>> themedCache; - final WeakReference<ConstantState<T>> wr; - synchronized (this) { - themedCache = mCache.get(themeKey); - if (themedCache == null) { - return null; - } - wr = themedCache.get(key); - } - if (wr == null) { - return null; - } - final ConstantState entry = wr.get(); + public T getInstance(long key, Resources.Theme theme) { + final ConstantState<T> entry = get(key, theme); if (entry != null) { - return (T) entry.newInstance(mResources, theme); - } else { // our entry has been purged - synchronized (this) { - // there is a potential race condition here where this entry may be put in - // another thread. But we prefer it to minimize lock duration - themedCache.delete(key); - } + return entry.newInstance(mResources, theme); } - return null; - } - /** - * Users of ConfigurationBoundResourceCache must call this method whenever a configuration - * change happens. On this callback, the cache invalidates all resources that are not valid - * anymore. - * - * @param configChanges The configuration changes - */ - public void onConfigurationChange(final int configChanges) { - synchronized (this) { - final int size = mCache.size(); - for (int i = size - 1; i >= 0; i--) { - final LongSparseArray<WeakReference<ConstantState<T>>> - themeCache = mCache.valueAt(i); - onConfigurationChangeInt(themeCache, configChanges); - if (themeCache.size() == 0) { - mCache.removeAt(i); - } - } - } + return null; } - private void onConfigurationChangeInt( - final LongSparseArray<WeakReference<ConstantState<T>>> themeCache, - final int configChanges) { - final int size = themeCache.size(); - for (int i = size - 1; i >= 0; i--) { - final WeakReference<ConstantState<T>> wr = themeCache.valueAt(i); - final ConstantState<T> constantState = wr.get(); - if (constantState == null || Configuration.needNewResources( - configChanges, constantState.getChangingConfigurations())) { - themeCache.removeAt(i); - } - } + @Override + public boolean shouldInvalidateEntry(ConstantState<T> entry, int configChanges) { + return Configuration.needNewResources(configChanges, entry.getChangingConfigurations()); } - } diff --git a/core/java/android/content/res/DrawableCache.java b/core/java/android/content/res/DrawableCache.java new file mode 100644 index 0000000..fc70bc6 --- /dev/null +++ b/core/java/android/content/res/DrawableCache.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 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.res; + +import android.graphics.drawable.Drawable; + +/** + * Class which can be used to cache Drawable resources against a theme. + */ +class DrawableCache extends ThemedResourceCache<Drawable.ConstantState> { + private final Resources mResources; + + /** + * Creates a cache for the given Resources instance. + * + * @param resources the resources to use when creating new instances + */ + public DrawableCache(Resources resources) { + mResources = resources; + } + + /** + * If the resource is cached, creates and returns a new instance of it. + * + * @param key a key that uniquely identifies the drawable resource + * @param theme the theme where the resource will be used + * @return a new instance of the resource, or {@code null} if not in + * the cache + */ + public Drawable getInstance(long key, Resources.Theme theme) { + final Drawable.ConstantState entry = get(key, theme); + if (entry != null) { + return entry.newDrawable(mResources, theme); + } + + return null; + } + + @Override + public boolean shouldInvalidateEntry(Drawable.ConstantState entry, int configChanges) { + return false; + } +} diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 6e77e33..1d108a2 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -20,6 +20,8 @@ import android.annotation.AttrRes; import android.annotation.ColorInt; import android.annotation.StyleRes; import android.annotation.StyleableRes; + +import com.android.internal.util.GrowingArrayUtils; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; @@ -132,10 +134,8 @@ public class Resources { // These are protected by mAccessLock. private final Object mAccessLock = new Object(); private final Configuration mTmpConfig = new Configuration(); - private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mDrawableCache = - new ArrayMap<>(); - private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mColorDrawableCache = - new ArrayMap<>(); + private final DrawableCache mDrawableCache = new DrawableCache(this); + private final DrawableCache mColorDrawableCache = new DrawableCache(this); private final ConfigurationBoundResourceCache<ColorStateList> mColorStateListCache = new ConfigurationBoundResourceCache<>(this); private final ConfigurationBoundResourceCache<Animator> mAnimatorCache = @@ -1441,7 +1441,7 @@ public class Resources { AssetManager.applyThemeStyle(mTheme, resId, force); mThemeResId = resId; - mKey += Integer.toHexString(resId) + (force ? "! " : " "); + mKey.append(resId, force); } /** @@ -1457,7 +1457,7 @@ public class Resources { AssetManager.copyTheme(mTheme, other.mTheme); mThemeResId = other.mThemeResId; - mKey = other.mKey; + mKey.setTo(other.getKey()); } /** @@ -1731,6 +1731,19 @@ public class Resources { } /** + * Returns a bit mask of configuration changes that will impact this + * theme (and thus require completely reloading it). + * + * @return a bit mask of configuration changes, as defined by + * {@link ActivityInfo} + * @see ActivityInfo + */ + public int getChangingConfigurations() { + final int nativeChangingConfig = AssetManager.getThemeChangingConfigurations(mTheme); + return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig); + } + + /** * Print contents of this theme out to the log. For debugging only. * * @param priority The log priority to use. @@ -1752,6 +1765,9 @@ public class Resources { mTheme = mAssets.createTheme(); } + /** Unique key for the series of styles applied to this theme. */ + private final ThemeKey mKey = new ThemeKey(); + @SuppressWarnings("hiding") private final AssetManager mAssets; private final long mTheme; @@ -1759,9 +1775,6 @@ public class Resources { /** Resource identifier for the theme. */ private int mThemeResId = 0; - /** Unique key for the series of styles applied to this theme. */ - private String mKey = ""; - // Needed by layoutlib. /*package*/ long getNativeTheme() { return mTheme; @@ -1771,7 +1784,7 @@ public class Resources { return mThemeResId; } - /*package*/ String getKey() { + /*package*/ ThemeKey getKey() { return mKey; } @@ -1780,29 +1793,119 @@ public class Resources { } /** - * Parses {@link #mKey} and returns a String array that holds pairs of adjacent Theme data: - * resource name followed by whether or not it was forced, as specified by - * {@link #applyStyle(int, boolean)}. + * Parses {@link #mKey} and returns a String array that holds pairs of + * adjacent Theme data: resource name followed by whether or not it was + * forced, as specified by {@link #applyStyle(int, boolean)}. * * @hide */ @ViewDebug.ExportedProperty(category = "theme", hasAdjacentMapping = true) public String[] getTheme() { - String[] themeData = mKey.split(" "); - String[] themes = new String[themeData.length * 2]; - String theme; - boolean forced; - - for (int i = 0, j = themeData.length - 1; i < themes.length; i += 2, --j) { - theme = themeData[j]; - forced = theme.endsWith("!"); - themes[i] = forced ? - getResourceNameFromHexString(theme.substring(0, theme.length() - 1)) : - getResourceNameFromHexString(theme); + final int N = mKey.mCount; + final String[] themes = new String[N * 2]; + for (int i = 0, j = N - 1; i < themes.length; i += 2, --j) { + final int resId = mKey.mResId[i]; + final boolean forced = mKey.mForce[i]; + themes[i] = getResourceName(resId); themes[i + 1] = forced ? "forced" : "not forced"; } return themes; } + + /** + * Rebases the theme against the parent Resource object's current + * configuration by re-applying the styles passed to + * {@link #applyStyle(int, boolean)}. + * + * @hide + */ + public void rebase() { + AssetManager.clearTheme(mTheme); + + // Reapply the same styles in the same order. + for (int i = 0; i < mKey.mCount; i++) { + final int resId = mKey.mResId[i]; + final boolean force = mKey.mForce[i]; + AssetManager.applyThemeStyle(mTheme, resId, force); + } + } + } + + static class ThemeKey implements Cloneable { + int[] mResId; + boolean[] mForce; + int mCount; + + private int mHashCode = 0; + + public void append(int resId, boolean force) { + if (mResId == null) { + mResId = new int[4]; + } + + if (mForce == null) { + mForce = new boolean[4]; + } + + mResId = GrowingArrayUtils.append(mResId, mCount, resId); + mForce = GrowingArrayUtils.append(mForce, mCount, force); + mCount++; + + mHashCode = 31 * (31 * mHashCode + resId) + (force ? 1 : 0); + } + + /** + * Sets up this key as a deep copy of another key. + * + * @param other the key to deep copy into this key + */ + public void setTo(ThemeKey other) { + mResId = other.mResId == null ? null : other.mResId.clone(); + mForce = other.mForce == null ? null : other.mForce.clone(); + mCount = other.mCount; + } + + @Override + public int hashCode() { + return mHashCode; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass() || hashCode() != o.hashCode()) { + return false; + } + + final ThemeKey t = (ThemeKey) o; + if (mCount != t.mCount) { + return false; + } + + final int N = mCount; + for (int i = 0; i < N; i++) { + if (mResId[i] != t.mResId[i] || mForce[i] != t.mForce[i]) { + return false; + } + } + + return true; + } + + /** + * @return a shallow copy of this key + */ + @Override + public ThemeKey clone() { + final ThemeKey other = new ThemeKey(); + other.mResId = mResId; + other.mForce = mForce; + other.mCount = mCount; + return other; + } } /** @@ -1931,8 +2034,8 @@ public class Resources { + " final compat is " + mCompatibilityInfo); } - clearDrawableCachesLocked(mDrawableCache, configChanges); - clearDrawableCachesLocked(mColorDrawableCache, configChanges); + mDrawableCache.onConfigurationChange(configChanges); + mColorDrawableCache.onConfigurationChange(configChanges); mColorStateListCache.onConfigurationChange(configChanges); mAnimatorCache.onConfigurationChange(configChanges); mStateListAnimatorCache.onConfigurationChange(configChanges); @@ -1970,48 +2073,6 @@ public class Resources { return configChanges; } - private void clearDrawableCachesLocked( - ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches, - int configChanges) { - final int N = caches.size(); - for (int i = 0; i < N; i++) { - clearDrawableCacheLocked(caches.valueAt(i), configChanges); - } - } - - private void clearDrawableCacheLocked( - LongSparseArray<WeakReference<ConstantState>> cache, int configChanges) { - if (DEBUG_CONFIG) { - Log.d(TAG, "Cleaning up drawables config changes: 0x" - + Integer.toHexString(configChanges)); - } - final int N = cache.size(); - for (int i = 0; i < N; i++) { - final WeakReference<ConstantState> ref = cache.valueAt(i); - if (ref != null) { - final ConstantState cs = ref.get(); - if (cs != null) { - if (Configuration.needNewResources( - configChanges, cs.getChangingConfigurations())) { - if (DEBUG_CONFIG) { - Log.d(TAG, "FLUSHING #0x" - + Long.toHexString(cache.keyAt(i)) - + " / " + cs + " with changes: 0x" - + Integer.toHexString(cs.getChangingConfigurations())); - } - cache.setValueAt(i, null); - } else if (DEBUG_CONFIG) { - Log.d(TAG, "(Keeping #0x" - + Long.toHexString(cache.keyAt(i)) - + " / " + cs + " with changes: 0x" - + Integer.toHexString(cs.getChangingConfigurations()) - + ")"); - } - } - } - } - } - /** * {@code Locale.toLanguageTag} will transform the obsolete (and deprecated) * language codes "in", "ji" and "iw" to "id", "yi" and "he" respectively. @@ -2423,7 +2484,7 @@ public class Resources { } final boolean isColorDrawable; - final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches; + final DrawableCache caches; final long key; if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { @@ -2439,7 +2500,7 @@ public class Resources { // First, check whether we have a cached version of this drawable // that was inflated against the specified theme. if (!mPreloading) { - final Drawable cachedDrawable = getCachedDrawable(caches, key, theme); + final Drawable cachedDrawable = caches.getInstance(key, theme); if (cachedDrawable != null) { return cachedDrawable; } @@ -2466,13 +2527,8 @@ public class Resources { // Determine if the drawable has unresolved theme attributes. If it // does, we'll need to apply a theme and store it in a theme-specific // cache. - final String cacheKey; - if (!dr.canApplyTheme()) { - cacheKey = CACHE_NOT_THEMED; - } else if (theme == null) { - cacheKey = CACHE_NULL_THEME; - } else { - cacheKey = theme.getKey(); + final boolean canApplyTheme = dr.canApplyTheme(); + if (canApplyTheme && theme != null) { dr = dr.mutate(); dr.applyTheme(theme); dr.clearMutated(); @@ -2482,15 +2538,14 @@ public class Resources { // cache: preload, not themed, null theme, or theme-specific. if (dr != null) { dr.setChangingConfigurations(value.changingConfigurations); - cacheDrawable(value, isColorDrawable, caches, cacheKey, key, dr); + cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr); } return dr; } - private void cacheDrawable(TypedValue value, boolean isColorDrawable, - ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches, - String cacheKey, long key, Drawable dr) { + private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches, + Theme theme, boolean usesTheme, long key, Drawable dr) { final ConstantState cs = dr.getConstantState(); if (cs == null) { return; @@ -2518,54 +2573,12 @@ public class Resources { } } else { synchronized (mAccessLock) { - LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(cacheKey); - if (themedCache == null) { - // Clean out the caches before we add more. This shouldn't - // happen very often. - pruneCaches(caches); - themedCache = new LongSparseArray<>(1); - caches.put(cacheKey, themedCache); - } - themedCache.put(key, new WeakReference<>(cs)); - } - } - } - - /** - * Prunes empty caches from the cache map. - * - * @param caches The map of caches to prune. - */ - private void pruneCaches(ArrayMap<String, - LongSparseArray<WeakReference<ConstantState>>> caches) { - final int N = caches.size(); - for (int i = N - 1; i >= 0; i--) { - final LongSparseArray<WeakReference<ConstantState>> cache = caches.valueAt(i); - if (pruneCache(cache)) { - caches.removeAt(i); + caches.put(key, theme, cs, usesTheme); } } } /** - * Prunes obsolete weak references from a cache, returning {@code true} if - * the cache is empty and should be removed. - * - * @param cache The cache of weak references to prune. - * @return {@code true} if the cache is empty and should be removed. - */ - private boolean pruneCache(LongSparseArray<WeakReference<ConstantState>> cache) { - final int N = cache.size(); - for (int i = N - 1; i >= 0; i--) { - final WeakReference entry = cache.valueAt(i); - if (entry == null || entry.get() == null) { - cache.removeAt(i); - } - } - return cache.size() == 0; - } - - /** * Loads a drawable from XML or resources stream. */ private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) { @@ -2618,51 +2631,6 @@ public class Resources { return dr; } - private Drawable getCachedDrawable( - ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches, - long key, Theme theme) { - synchronized (mAccessLock) { - // First search theme-agnostic cache. - final Drawable unthemedDrawable = getCachedDrawableLocked( - caches, key, CACHE_NOT_THEMED); - if (unthemedDrawable != null) { - return unthemedDrawable; - } - - // Next search theme-specific cache. - final String themeKey = theme != null ? theme.getKey() : CACHE_NULL_THEME; - return getCachedDrawableLocked(caches, key, themeKey); - } - } - - private Drawable getCachedDrawableLocked( - ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches, - long key, String themeKey) { - final LongSparseArray<WeakReference<ConstantState>> cache = caches.get(themeKey); - if (cache != null) { - final ConstantState entry = getConstantStateLocked(cache, key); - if (entry != null) { - return entry.newDrawable(this); - } - } - return null; - } - - private ConstantState getConstantStateLocked( - LongSparseArray<WeakReference<ConstantState>> drawableCache, long key) { - final WeakReference<ConstantState> wr = drawableCache.get(key); - if (wr != null) { - final ConstantState entry = wr.get(); - if (entry != null) { - return entry; - } else { - // Our entry has been purged. - drawableCache.delete(key); - } - } - return null; - } - @Nullable ColorStateList loadColorStateList(TypedValue value, int id, Theme theme) throws NotFoundException { @@ -2700,8 +2668,7 @@ public class Resources { } final ConfigurationBoundResourceCache<ColorStateList> cache = mColorStateListCache; - - csl = cache.get(key, theme); + csl = cache.getInstance(key, theme); if (csl != null) { return csl; } diff --git a/core/java/android/content/res/ThemedResourceCache.java b/core/java/android/content/res/ThemedResourceCache.java new file mode 100644 index 0000000..9a2d06147 --- /dev/null +++ b/core/java/android/content/res/ThemedResourceCache.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2015 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.res; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.res.Resources.Theme; +import android.content.res.Resources.ThemeKey; +import android.util.LongSparseArray; +import android.util.ArrayMap; + +import java.lang.ref.WeakReference; + +/** + * Data structure used for caching data against themes. + * + * @param <T> type of data to cache + */ +abstract class ThemedResourceCache<T> { + private ArrayMap<ThemeKey, LongSparseArray<WeakReference<T>>> mThemedEntries; + private LongSparseArray<WeakReference<T>> mUnthemedEntries; + private LongSparseArray<WeakReference<T>> mNullThemedEntries; + + /** + * Adds a new theme-dependent entry to the cache. + * + * @param key a key that uniquely identifies the entry + * @param theme the theme against which this entry was inflated, or + * {@code null} if the entry has no theme applied + * @param entry the entry to cache + */ + public void put(long key, @Nullable Theme theme, @NonNull T entry) { + put(key, theme, entry, true); + } + + /** + * Adds a new entry to the cache. + * + * @param key a key that uniquely identifies the entry + * @param theme the theme against which this entry was inflated, or + * {@code null} if the entry has no theme applied + * @param entry the entry to cache + * @param usesTheme {@code true} if the entry is affected theme changes, + * {@code false} otherwise + */ + public void put(long key, @Nullable Theme theme, @NonNull T entry, boolean usesTheme) { + if (entry == null) { + return; + } + + synchronized (this) { + final LongSparseArray<WeakReference<T>> entries; + if (!usesTheme) { + entries = getUnthemedLocked(true); + } else { + entries = getThemedLocked(theme, true); + } + if (entries != null) { + entries.put(key, new WeakReference<>(entry)); + } + } + } + + /** + * Returns an entry from the cache. + * + * @param key a key that uniquely identifies the entry + * @param theme the theme where the entry will be used + * @return a cached entry, or {@code null} if not in the cache + */ + @Nullable + public T get(long key, @Nullable Theme theme) { + // The themed (includes null-themed) and unthemed caches are mutually + // exclusive, so we'll give priority to whichever one we think we'll + // hit first. Since most of the framework drawables are themed, that's + // probably going to be the themed cache. + synchronized (this) { + final LongSparseArray<WeakReference<T>> themedEntries = getThemedLocked(theme, false); + if (themedEntries != null) { + final WeakReference<T> themedEntry = themedEntries.get(key); + if (themedEntry != null) { + return themedEntry.get(); + } + } + + final LongSparseArray<WeakReference<T>> unthemedEntries = getUnthemedLocked(false); + if (unthemedEntries != null) { + final WeakReference<T> unthemedEntry = unthemedEntries.get(key); + if (unthemedEntry != null) { + return unthemedEntry.get(); + } + } + } + + return null; + } + + /** + * Prunes cache entries that have been invalidated by a configuration + * change. + * + * @param configChanges a bitmask of configuration changes + */ + public void onConfigurationChange(int configChanges) { + prune(configChanges); + } + + /** + * Returns whether a cached entry has been invalidated by a configuration + * change. + * + * @param entry a cached entry + * @param configChanges a non-zero bitmask of configuration changes + * @return {@code true} if the entry is invalid, {@code false} otherwise + */ + protected abstract boolean shouldInvalidateEntry(@NonNull T entry, int configChanges); + + /** + * Returns the cached data for the specified theme, optionally creating a + * new entry if one does not already exist. + * + * @param t the theme for which to return cached data + * @param create {@code true} to create an entry if one does not already + * exist, {@code false} otherwise + * @return the cached data for the theme, or {@code null} if the cache is + * empty and {@code create} was {@code false} + */ + @Nullable + private LongSparseArray<WeakReference<T>> getThemedLocked(@Nullable Theme t, boolean create) { + if (t == null) { + if (mNullThemedEntries == null && create) { + mNullThemedEntries = new LongSparseArray<>(1); + } + return mNullThemedEntries; + } + + if (mThemedEntries == null) { + if (create) { + mThemedEntries = new ArrayMap<>(1); + } else { + return null; + } + } + + final ThemeKey key = t.getKey(); + LongSparseArray<WeakReference<T>> cache = mThemedEntries.get(key); + if (cache == null && create) { + cache = new LongSparseArray<>(1); + + final ThemeKey keyClone = key.clone(); + mThemedEntries.put(keyClone, cache); + } + + return cache; + } + + /** + * Returns the theme-agnostic cached data. + * + * @param create {@code true} to create an entry if one does not already + * exist, {@code false} otherwise + * @return the theme-agnostic cached data, or {@code null} if the cache is + * empty and {@code create} was {@code false} + */ + @Nullable + private LongSparseArray<WeakReference<T>> getUnthemedLocked(boolean create) { + if (mUnthemedEntries == null && create) { + mUnthemedEntries = new LongSparseArray<>(1); + } + return mUnthemedEntries; + } + + /** + * Prunes cache entries affected by configuration changes or where weak + * references have expired. + * + * @param configChanges a bitmask of configuration changes, or {@code 0} to + * simply prune missing weak references + * @return {@code true} if the cache is completely empty after pruning + */ + private boolean prune(int configChanges) { + synchronized (this) { + if (mThemedEntries != null) { + for (int i = mThemedEntries.size() - 1; i >= 0; i--) { + if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) { + mThemedEntries.removeAt(i); + } + } + } + + pruneEntriesLocked(mNullThemedEntries, configChanges); + pruneEntriesLocked(mUnthemedEntries, configChanges); + + return mThemedEntries == null && mNullThemedEntries == null + && mUnthemedEntries == null; + } + } + + private boolean pruneEntriesLocked(@Nullable LongSparseArray<WeakReference<T>> entries, + int configChanges) { + if (entries == null) { + return true; + } + + for (int i = entries.size() - 1; i >= 0; i--) { + final WeakReference<T> ref = entries.valueAt(i); + if (ref == null || pruneEntryLocked(ref.get(), configChanges)) { + entries.removeAt(i); + } + } + + return entries.size() == 0; + } + + private boolean pruneEntryLocked(@Nullable T entry, int configChanges) { + return entry == null || (configChanges != 0 + && shouldInvalidateEntry(entry, configChanges)); + } +} diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index aeddf03..ef71c42 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -212,8 +212,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * <p>All capture sessions can be used for capturing images from the camera but only capture * sessions created by * {@link CameraDevice#createReprocessibleCaptureSession createReprocessibleCaptureSession} - * can submit reprocess capture requests. The list of requests must all be capturing images from - * the camera or all be reprocess capture requests. Submitting a reprocess request to a regular + * can submit reprocess capture requests. Submitting a reprocess request to a regular * capture session will result in an {@link IllegalArgumentException}.</p> * * @param requests the list of settings for this burst capture @@ -236,9 +235,7 @@ public abstract class CameraCaptureSession implements AutoCloseable { * @throws IllegalArgumentException If the requests target no Surfaces, or the requests target * Surfaces not currently configured as outputs; or a reprocess * capture request is submitted in a non-reprocessible capture - * session; or the list of requests contains both requests to - * capture images from the camera and reprocess capture - * requests; or one of the reprocess capture requests was + * session; or one of the reprocess capture requests was * created with a {@link TotalCaptureResult} from a different * session; or one of the captures targets a Surface in the * middle of being {@link #prepare prepared}; or if the handler diff --git a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java index 3c19529..dff6227 100644 --- a/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraCaptureSessionImpl.java @@ -177,26 +177,20 @@ public class CameraCaptureSessionImpl extends CameraCaptureSession { public synchronized int captureBurst(List<CaptureRequest> requests, CaptureCallback callback, Handler handler) throws CameraAccessException { if (requests == null) { - throw new IllegalArgumentException("requests must not be null"); + throw new IllegalArgumentException("Requests must not be null"); } else if (requests.isEmpty()) { - throw new IllegalArgumentException("requests must have at least one element"); - } - - boolean reprocess = requests.get(0).isReprocess(); - if (reprocess && !isReprocessible()) { - throw new IllegalArgumentException("this capture session cannot handle reprocess " + - "requests"); - } else if (reprocess && requests.get(0).getReprocessibleSessionId() != mId) { - throw new IllegalArgumentException("capture request was created for another session"); + throw new IllegalArgumentException("Requests must have at least one element"); } - for (int i = 1; i < requests.size(); i++) { - if (requests.get(i).isReprocess() != reprocess) { - throw new IllegalArgumentException("cannot mix regular and reprocess capture " + - " requests"); - } else if (reprocess && requests.get(i).getReprocessibleSessionId() != mId) { - throw new IllegalArgumentException("capture request was created for another " + - "session"); + for (CaptureRequest request : requests) { + if (request.isReprocess()) { + if (!isReprocessible()) { + throw new IllegalArgumentException("This capture session cannot handle " + + "reprocess requests"); + } else if (request.getReprocessibleSessionId() != mId) { + throw new IllegalArgumentException("Capture request was created for another " + + "session"); + } } } diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index ff4ad79..e84b46a 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -94,11 +94,11 @@ public class CameraDeviceImpl extends CameraDevice { private final int mTotalPartialCount; /** - * A list tracking request and its expected last frame. - * Updated when calling ICameraDeviceUser methods. + * A list tracking request and its expected last regular frame number and last reprocess frame + * number. Updated when calling ICameraDeviceUser methods. */ - private final List<SimpleEntry</*frameNumber*/Long, /*requestId*/Integer>> - mFrameNumberRequestPairs = new ArrayList<SimpleEntry<Long, Integer>>(); + private final List<RequestLastFrameNumbersHolder> mRequestLastFrameNumbersList = + new ArrayList<>(); /** * An object tracking received frame numbers. @@ -653,8 +653,8 @@ public class CameraDeviceImpl extends CameraDevice { * * <p>If lastFrameNumber is NO_FRAMES_CAPTURED, it means that the request was never * sent to HAL. Then onCaptureSequenceAborted is immediately triggered. - * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber pair - * is added to the list mFrameNumberRequestPairs.</p> + * If lastFrameNumber is non-negative, then the requestId and lastFrameNumber as the last + * regular frame number will be added to the list mRequestLastFrameNumbersList.</p> * * @param requestId the request ID of the current repeating request. * @@ -693,10 +693,6 @@ public class CameraDeviceImpl extends CameraDevice { "early trigger sequence complete for request %d", requestId)); } - if (lastFrameNumber < Integer.MIN_VALUE - || lastFrameNumber > Integer.MAX_VALUE) { - throw new AssertionError(lastFrameNumber + " cannot be cast to int"); - } holder.getCallback().onCaptureSequenceAborted( CameraDeviceImpl.this, requestId); @@ -710,9 +706,11 @@ public class CameraDeviceImpl extends CameraDevice { requestId)); } } else { - mFrameNumberRequestPairs.add( - new SimpleEntry<Long, Integer>(lastFrameNumber, - requestId)); + // This function is only called for regular request so lastFrameNumber is the last + // regular frame number. + mRequestLastFrameNumbersList.add(new RequestLastFrameNumbersHolder(requestId, + lastFrameNumber)); + // It is possible that the last frame has already arrived, so we need to check // for sequence completion right away checkAndFireSequenceComplete(); @@ -779,8 +777,8 @@ public class CameraDeviceImpl extends CameraDevice { } mRepeatingRequestId = requestId; } else { - mFrameNumberRequestPairs.add( - new SimpleEntry<Long, Integer>(lastFrameNumber, requestId)); + mRequestLastFrameNumbersList.add(new RequestLastFrameNumbersHolder(requestList, + requestId, lastFrameNumber)); } if (mIdle) { @@ -1146,7 +1144,101 @@ public class CameraDeviceImpl extends CameraDevice { public int getSessionId() { return mSessionId; } + } + + /** + * This class holds a capture ID and its expected last regular frame number and last reprocess + * frame number. + */ + static class RequestLastFrameNumbersHolder { + // request ID + private final int mRequestId; + // The last regular frame number for this request ID. It's + // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no regular request. + private final long mLastRegularFrameNumber; + // The last reprocess frame number for this request ID. It's + // CaptureCallback.NO_FRAMES_CAPTURED if the request ID has no reprocess request. + private final long mLastReprocessFrameNumber; + + /** + * Create a request-last-frame-numbers holder with a list of requests, request ID, and + * the last frame number returned by camera service. + */ + public RequestLastFrameNumbersHolder(List<CaptureRequest> requestList, int requestId, + long lastFrameNumber) { + long lastRegularFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; + long lastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; + long frameNumber = lastFrameNumber; + + if (lastFrameNumber < requestList.size() - 1) { + throw new IllegalArgumentException("lastFrameNumber: " + lastFrameNumber + + " should be at least " + (requestList.size() - 1) + " for the number of " + + " requests in the list: " + requestList.size()); + } + + // find the last regular frame number and the last reprocess frame number + for (int i = requestList.size() - 1; i >= 0; i--) { + CaptureRequest request = requestList.get(i); + if (request.isReprocess() && lastReprocessFrameNumber == + CaptureCallback.NO_FRAMES_CAPTURED) { + lastReprocessFrameNumber = frameNumber; + } else if (!request.isReprocess() && lastRegularFrameNumber == + CaptureCallback.NO_FRAMES_CAPTURED) { + lastRegularFrameNumber = frameNumber; + } + + if (lastReprocessFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED && + lastRegularFrameNumber != CaptureCallback.NO_FRAMES_CAPTURED) { + break; + } + + frameNumber--; + } + + mLastRegularFrameNumber = lastRegularFrameNumber; + mLastReprocessFrameNumber = lastReprocessFrameNumber; + mRequestId = requestId; + } + + /** + * Create a request-last-frame-numbers holder with a request ID and last regular frame + * number. + */ + public RequestLastFrameNumbersHolder(int requestId, long lastRegularFrameNumber) { + mLastRegularFrameNumber = lastRegularFrameNumber; + mLastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; + mRequestId = requestId; + } + + /** + * Return the last regular frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if + * it contains no regular request. + */ + public long getLastRegularFrameNumber() { + return mLastRegularFrameNumber; + } + + /** + * Return the last reprocess frame number. Return CaptureCallback.NO_FRAMES_CAPTURED if + * it contains no reprocess request. + */ + public long getLastReprocessFrameNumber() { + return mLastReprocessFrameNumber; + } + /** + * Return the last frame number overall. + */ + public long getLastFrameNumber() { + return Math.max(mLastRegularFrameNumber, mLastReprocessFrameNumber); + } + + /** + * Return the request ID. + */ + public int getRequestId() { + return mRequestId; + } } /** @@ -1154,8 +1246,8 @@ public class CameraDeviceImpl extends CameraDevice { */ public class FrameNumberTracker { - private long mCompletedFrameNumber = -1; - private long mCompletedReprocessFrameNumber = -1; + private long mCompletedFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; + private long mCompletedReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED; /** the skipped frame numbers that belong to regular results */ private final LinkedList<Long> mSkippedRegularFrameNumbers = new LinkedList<Long>(); /** the skipped frame numbers that belong to reprocess results */ @@ -1360,11 +1452,11 @@ public class CameraDeviceImpl extends CameraDevice { long completedFrameNumber = mFrameNumberTracker.getCompletedFrameNumber(); long completedReprocessFrameNumber = mFrameNumberTracker.getCompletedReprocessFrameNumber(); boolean isReprocess = false; - Iterator<SimpleEntry<Long, Integer> > iter = mFrameNumberRequestPairs.iterator(); + Iterator<RequestLastFrameNumbersHolder> iter = mRequestLastFrameNumbersList.iterator(); while (iter.hasNext()) { - final SimpleEntry<Long, Integer> frameNumberRequestPair = iter.next(); + final RequestLastFrameNumbersHolder requestLastFrameNumbers = iter.next(); boolean sequenceCompleted = false; - final int requestId = frameNumberRequestPair.getValue(); + final int requestId = requestLastFrameNumbers.getRequestId(); final CaptureCallbackHolder holder; synchronized(mInterfaceLock) { if (mRemoteDevice == null) { @@ -1376,19 +1468,22 @@ public class CameraDeviceImpl extends CameraDevice { holder = (index >= 0) ? mCaptureCallbackMap.valueAt(index) : null; if (holder != null) { - isReprocess = holder.getRequest().isReprocess(); + long lastRegularFrameNumber = + requestLastFrameNumbers.getLastRegularFrameNumber(); + long lastReprocessFrameNumber = + requestLastFrameNumbers.getLastReprocessFrameNumber(); + // check if it's okay to remove request from mCaptureCallbackMap - if ((isReprocess && frameNumberRequestPair.getKey() <= - completedReprocessFrameNumber) || (!isReprocess && - frameNumberRequestPair.getKey() <= completedFrameNumber)) { + if (lastRegularFrameNumber <= completedFrameNumber && + lastReprocessFrameNumber <= completedReprocessFrameNumber) { sequenceCompleted = true; mCaptureCallbackMap.removeAt(index); if (DEBUG) { Log.v(TAG, String.format( - "remove holder for requestId %d, " - + "because lastFrame %d is <= %d", - requestId, frameNumberRequestPair.getKey(), - completedFrameNumber)); + "Remove holder for requestId %d, because lastRegularFrame %d " + + "is <= %d and lastReprocessFrame %d is <= %d", requestId, + lastRegularFrameNumber, completedFrameNumber, + lastReprocessFrameNumber, completedReprocessFrameNumber)); } } } @@ -1412,16 +1507,10 @@ public class CameraDeviceImpl extends CameraDevice { requestId)); } - long lastFrameNumber = frameNumberRequestPair.getKey(); - if (lastFrameNumber < Integer.MIN_VALUE - || lastFrameNumber > Integer.MAX_VALUE) { - throw new AssertionError(lastFrameNumber - + " cannot be cast to int"); - } holder.getCallback().onCaptureSequenceCompleted( CameraDeviceImpl.this, requestId, - lastFrameNumber); + requestLastFrameNumbers.getLastFrameNumber()); } } }; diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java index a4d6be0..691798f 100644 --- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java +++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java @@ -326,9 +326,6 @@ public class RequestThreadManager { } try { - startPreview(); // If preview is not running (i.e. after a JPEG capture), we need to - // explicitely start and stop preview before setting preview surface. - // null. stopPreview(); } catch (RuntimeException e) { Log.e(TAG, "Received device exception in configure call: ", e); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 293cf6f..a7af838 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -324,23 +324,7 @@ public final class Settings { "android.settings.BLUETOOTH_SETTINGS"; /** - * Activity Action: Show settings to allow configuration of Wifi Displays. - * <p> - * In some cases, a matching Activity may not exist, so ensure you - * safeguard against this. - * <p> - * Input: Nothing. - * <p> - * Output: Nothing. - * @hide - */ - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_WIFI_DISPLAY_SETTINGS = - "android.settings.WIFI_DISPLAY_SETTINGS"; - - /** - * Activity Action: Show settings to allow configuration of - * {@link android.media.routing.MediaRouteService media route providers}. + * Activity Action: Show settings to allow configuration of cast endpoints. * <p> * In some cases, a matching Activity may not exist, so ensure you * safeguard against this. @@ -5951,6 +5935,12 @@ public final class Settings { "wireless_charging_started_sound"; /** + * Whether to play a sound for charging events. + * @hide + */ + public static final String CHARGING_SOUNDS_ENABLED = "charging_sounds_enabled"; + + /** * Whether we keep the device on while the device is plugged in. * Supported values are: * <ul> diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 29aaf30..0171869 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -40,7 +40,7 @@ import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; -import android.view.PhoneWindow; +import com.android.internal.policy.PhoneWindow; import android.view.SearchEvent; import android.view.View; import android.view.ViewGroup; diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index f09f4d2..26bd10f 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -55,6 +55,7 @@ public class ZenModeConfig implements Parcelable { public static final int SOURCE_CONTACT = 1; public static final int SOURCE_STAR = 2; public static final int MAX_SOURCE = SOURCE_STAR; + private static final int DEFAULT_SOURCE = SOURCE_CONTACT; public static final int[] ALL_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY }; @@ -67,6 +68,8 @@ public class ZenModeConfig implements Parcelable { private static final int MINUTES_MS = 60 * SECONDS_MS; private static final int ZERO_VALUE_MS = 10 * SECONDS_MS; + private static final boolean DEFAULT_ALLOW_CALLS = true; + private static final boolean DEFAULT_ALLOW_MESSAGES = false; private static final boolean DEFAULT_ALLOW_REMINDERS = true; private static final boolean DEFAULT_ALLOW_EVENTS = true; private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = false; @@ -79,6 +82,8 @@ public class ZenModeConfig implements Parcelable { private static final String ALLOW_ATT_REPEAT_CALLERS = "repeatCallers"; private static final String ALLOW_ATT_MESSAGES = "messages"; private static final String ALLOW_ATT_FROM = "from"; + private static final String ALLOW_ATT_CALLS_FROM = "callsFrom"; + private static final String ALLOW_ATT_MESSAGES_FROM = "messagesFrom"; private static final String ALLOW_ATT_REMINDERS = "reminders"; private static final String ALLOW_ATT_EVENTS = "events"; @@ -103,12 +108,13 @@ public class ZenModeConfig implements Parcelable { private static final String RULE_ATT_ZEN = "zen"; private static final String RULE_ATT_CONDITION_ID = "conditionId"; - public boolean allowCalls; + public boolean allowCalls = DEFAULT_ALLOW_CALLS; public boolean allowRepeatCallers = DEFAULT_ALLOW_REPEAT_CALLERS; - public boolean allowMessages; + public boolean allowMessages = DEFAULT_ALLOW_MESSAGES; public boolean allowReminders = DEFAULT_ALLOW_REMINDERS; public boolean allowEvents = DEFAULT_ALLOW_EVENTS; - public int allowFrom = SOURCE_ANYONE; + public int allowCallsFrom = DEFAULT_SOURCE; + public int allowMessagesFrom = DEFAULT_SOURCE; public ZenRule manualRule; public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>(); @@ -121,7 +127,8 @@ public class ZenModeConfig implements Parcelable { allowMessages = source.readInt() == 1; allowReminders = source.readInt() == 1; allowEvents = source.readInt() == 1; - allowFrom = source.readInt(); + allowCallsFrom = source.readInt(); + allowMessagesFrom = source.readInt(); manualRule = source.readParcelable(null); final int len = source.readInt(); if (len > 0) { @@ -142,7 +149,8 @@ public class ZenModeConfig implements Parcelable { dest.writeInt(allowMessages ? 1 : 0); dest.writeInt(allowReminders ? 1 : 0); dest.writeInt(allowEvents ? 1 : 0); - dest.writeInt(allowFrom); + dest.writeInt(allowCallsFrom); + dest.writeInt(allowMessagesFrom); dest.writeParcelable(manualRule, 0); if (!automaticRules.isEmpty()) { final int len = automaticRules.size(); @@ -166,7 +174,8 @@ public class ZenModeConfig implements Parcelable { .append("allowCalls=").append(allowCalls) .append(",allowRepeatCallers=").append(allowRepeatCallers) .append(",allowMessages=").append(allowMessages) - .append(",allowFrom=").append(sourceToString(allowFrom)) + .append(",allowCallsFrom=").append(sourceToString(allowCallsFrom)) + .append(",allowMessagesFrom=").append(sourceToString(allowMessagesFrom)) .append(",allowReminders=").append(allowReminders) .append(",allowEvents=").append(allowEvents) .append(",automaticRules=").append(automaticRules) @@ -234,7 +243,8 @@ public class ZenModeConfig implements Parcelable { return other.allowCalls == allowCalls && other.allowRepeatCallers == allowRepeatCallers && other.allowMessages == allowMessages - && other.allowFrom == allowFrom + && other.allowCallsFrom == allowCallsFrom + && other.allowMessagesFrom == allowMessagesFrom && other.allowReminders == allowReminders && other.allowEvents == allowEvents && Objects.equals(other.automaticRules, automaticRules) @@ -243,8 +253,8 @@ public class ZenModeConfig implements Parcelable { @Override public int hashCode() { - return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowFrom, - allowReminders, allowEvents, automaticRules, manualRule); + return Objects.hash(allowCalls, allowRepeatCallers, allowMessages, allowCallsFrom, + allowMessagesFrom, allowReminders, allowEvents, automaticRules, manualRule); } private static String toDayList(int[] days) { @@ -314,9 +324,19 @@ public class ZenModeConfig implements Parcelable { rt.allowReminders = safeBoolean(parser, ALLOW_ATT_REMINDERS, DEFAULT_ALLOW_REMINDERS); rt.allowEvents = safeBoolean(parser, ALLOW_ATT_EVENTS, DEFAULT_ALLOW_EVENTS); - rt.allowFrom = safeInt(parser, ALLOW_ATT_FROM, SOURCE_ANYONE); - if (rt.allowFrom < SOURCE_ANYONE || rt.allowFrom > MAX_SOURCE) { - throw new IndexOutOfBoundsException("bad source in config:" + rt.allowFrom); + final int from = safeInt(parser, ALLOW_ATT_FROM, -1); + final int callsFrom = safeInt(parser, ALLOW_ATT_CALLS_FROM, -1); + final int messagesFrom = safeInt(parser, ALLOW_ATT_MESSAGES_FROM, -1); + if (isValidSource(callsFrom) && isValidSource(messagesFrom)) { + rt.allowCallsFrom = callsFrom; + rt.allowMessagesFrom = messagesFrom; + } else if (isValidSource(from)) { + Slog.i(TAG, "Migrating existing shared 'from': " + sourceToString(from)); + rt.allowCallsFrom = from; + rt.allowMessagesFrom = from; + } else { + rt.allowCallsFrom = DEFAULT_SOURCE; + rt.allowMessagesFrom = DEFAULT_SOURCE; } } else if (MANUAL_TAG.equals(tag)) { rt.manualRule = readRuleXml(parser, false /*conditionRequired*/); @@ -342,7 +362,8 @@ public class ZenModeConfig implements Parcelable { out.attribute(null, ALLOW_ATT_MESSAGES, Boolean.toString(allowMessages)); out.attribute(null, ALLOW_ATT_REMINDERS, Boolean.toString(allowReminders)); out.attribute(null, ALLOW_ATT_EVENTS, Boolean.toString(allowEvents)); - out.attribute(null, ALLOW_ATT_FROM, Integer.toString(allowFrom)); + out.attribute(null, ALLOW_ATT_CALLS_FROM, Integer.toString(allowCallsFrom)); + out.attribute(null, ALLOW_ATT_MESSAGES_FROM, Integer.toString(allowMessagesFrom)); out.endTag(null, ALLOW_TAG); if (manualRule != null) { @@ -432,6 +453,10 @@ public class ZenModeConfig implements Parcelable { return val >= 0 && val < 60; } + private static boolean isValidSource(int source) { + return source >= SOURCE_ANYONE && source <= MAX_SOURCE; + } + private static boolean safeBoolean(XmlPullParser parser, String att, boolean defValue) { final String val = parser.getAttributeValue(null, att); if (TextUtils.isEmpty(val)) return defValue; @@ -494,7 +519,8 @@ public class ZenModeConfig implements Parcelable { public Policy toNotificationPolicy() { int priorityCategories = 0; - int prioritySenders = Policy.PRIORITY_SENDERS_ANY; + int priorityCallSenders = Policy.PRIORITY_SENDERS_CONTACTS; + int priorityMessageSenders = Policy.PRIORITY_SENDERS_CONTACTS; if (allowCalls) { priorityCategories |= Policy.PRIORITY_CATEGORY_CALLS; } @@ -510,18 +536,27 @@ public class ZenModeConfig implements Parcelable { if (allowRepeatCallers) { priorityCategories |= Policy.PRIORITY_CATEGORY_REPEAT_CALLERS; } - switch (allowFrom) { - case SOURCE_ANYONE: - prioritySenders = Policy.PRIORITY_SENDERS_ANY; - break; - case SOURCE_CONTACT: - prioritySenders = Policy.PRIORITY_SENDERS_CONTACTS; - break; - case SOURCE_STAR: - prioritySenders = Policy.PRIORITY_SENDERS_STARRED; - break; + priorityCallSenders = sourceToPrioritySenders(allowCallsFrom, priorityCallSenders); + priorityMessageSenders = sourceToPrioritySenders(allowMessagesFrom, priorityMessageSenders); + return new Policy(priorityCategories, priorityCallSenders); + } + + private static int sourceToPrioritySenders(int source, int def) { + switch (source) { + case SOURCE_ANYONE: return Policy.PRIORITY_SENDERS_ANY; + case SOURCE_CONTACT: return Policy.PRIORITY_SENDERS_CONTACTS; + case SOURCE_STAR: return Policy.PRIORITY_SENDERS_STARRED; + default: return def; + } + } + + private static int prioritySendersToSource(int prioritySenders, int def) { + switch (prioritySenders) { + case Policy.PRIORITY_SENDERS_CONTACTS: return SOURCE_CONTACT; + case Policy.PRIORITY_SENDERS_STARRED: return SOURCE_STAR; + case Policy.PRIORITY_SENDERS_ANY: return SOURCE_ANYONE; + default: return def; } - return new Policy(priorityCategories, prioritySenders); } public void applyNotificationPolicy(Policy policy) { @@ -532,17 +567,7 @@ public class ZenModeConfig implements Parcelable { allowReminders = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REMINDERS) != 0; allowRepeatCallers = (policy.priorityCategories & Policy.PRIORITY_CATEGORY_REPEAT_CALLERS) != 0; - switch (policy.prioritySenders) { - case Policy.PRIORITY_SENDERS_CONTACTS: - allowFrom = SOURCE_CONTACT; - break; - case Policy.PRIORITY_SENDERS_STARRED: - allowFrom = SOURCE_STAR; - break; - default: - allowFrom = SOURCE_ANYONE; - break; - } + allowCallsFrom = prioritySendersToSource(policy.prioritySenders, allowCallsFrom); } public static Condition toTimeCondition(Context context, int minutesFromNow, int userHandle) { @@ -692,7 +717,6 @@ public class ZenModeConfig implements Parcelable { .authority(SYSTEM_AUTHORITY) .appendPath(EVENT_PATH) .appendQueryParameter("calendar", Long.toString(event.calendar)) - .appendQueryParameter("attendance", Integer.toString(event.attendance)) .appendQueryParameter("reply", Integer.toString(event.reply)) .build(); } @@ -710,22 +734,16 @@ public class ZenModeConfig implements Parcelable { if (!isEvent) return null; final EventInfo rt = new EventInfo(); rt.calendar = tryParseLong(conditionId.getQueryParameter("calendar"), 0L); - rt.attendance = tryParseInt(conditionId.getQueryParameter("attendance"), 0); rt.reply = tryParseInt(conditionId.getQueryParameter("reply"), 0); return rt; } public static class EventInfo { - public static final int ATTENDANCE_REQUIRED_OR_OPTIONAL = 0; - public static final int ATTENDANCE_REQUIRED = 1; - public static final int ATTENDANCE_OPTIONAL = 2; - - public static final int REPLY_ANY = 0; - public static final int REPLY_ANY_EXCEPT_NO = 1; + public static final int REPLY_ANY_EXCEPT_NO = 0; + public static final int REPLY_YES_OR_MAYBE = 1; public static final int REPLY_YES = 2; public long calendar; // CalendarContract.Calendars._ID, or 0 for any - public int attendance; public int reply; @Override @@ -738,14 +756,12 @@ public class ZenModeConfig implements Parcelable { if (!(o instanceof EventInfo)) return false; final EventInfo other = (EventInfo) o; return calendar == other.calendar - && attendance == other.attendance && reply == other.reply; } public EventInfo copy() { final EventInfo rt = new EventInfo(); rt.calendar = calendar; - rt.attendance = attendance; rt.reply = reply; return rt; } diff --git a/core/java/android/service/voice/IVoiceInteractionService.aidl b/core/java/android/service/voice/IVoiceInteractionService.aidl index e8265a2..e3d68a6 100644 --- a/core/java/android/service/voice/IVoiceInteractionService.aidl +++ b/core/java/android/service/voice/IVoiceInteractionService.aidl @@ -23,4 +23,5 @@ oneway interface IVoiceInteractionService { void ready(); void soundModelsChanged(); void shutdown(); + void launchVoiceAssistFromKeyguard(); } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index fee0c75..8c89ddb 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -98,6 +98,10 @@ public class VoiceInteractionService extends Service { @Override public void soundModelsChanged() { mHandler.sendEmptyMessage(MSG_SOUND_MODELS_CHANGED); } + @Override + public void launchVoiceAssistFromKeyguard() throws RemoteException { + mHandler.sendEmptyMessage(MSG_LAUNCH_VOICE_ASSIST_FROM_KEYGUARD); + } }; MyHandler mHandler; @@ -113,6 +117,7 @@ public class VoiceInteractionService extends Service { static final int MSG_READY = 1; static final int MSG_SHUTDOWN = 2; static final int MSG_SOUND_MODELS_CHANGED = 3; + static final int MSG_LAUNCH_VOICE_ASSIST_FROM_KEYGUARD = 4; class MyHandler extends Handler { @Override @@ -127,6 +132,9 @@ public class VoiceInteractionService extends Service { case MSG_SOUND_MODELS_CHANGED: onSoundModelsChangedInternal(); break; + case MSG_LAUNCH_VOICE_ASSIST_FROM_KEYGUARD: + onLaunchVoiceAssistFromKeyguard(); + break; default: super.handleMessage(msg); } @@ -134,6 +142,19 @@ public class VoiceInteractionService extends Service { } /** + * Called when a user has activated an affordance to launch voice assist from the Keyguard. + * + * <p>This method will only be called if the VoiceInteractionService has set + * {@link android.R.attr#supportsLaunchVoiceAssistFromKeyguard} and the Keyguard is showing.</p> + * + * <p>A valid implementation must start a new activity that should use {@link + * android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} to display + * on top of the lock screen.</p> + */ + public void onLaunchVoiceAssistFromKeyguard() { + } + + /** * Check whether the given service component is the currently active * VoiceInteractionService. */ diff --git a/core/java/android/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java index 997d586..463eb5b 100644 --- a/core/java/android/service/voice/VoiceInteractionServiceInfo.java +++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java @@ -44,6 +44,7 @@ public class VoiceInteractionServiceInfo { private String mRecognitionService; private String mSettingsActivity; private boolean mSupportsAssist; + private boolean mSupportsLaunchFromKeyguard; public VoiceInteractionServiceInfo(PackageManager pm, ComponentName comp) throws PackageManager.NameNotFoundException { @@ -98,6 +99,9 @@ public class VoiceInteractionServiceInfo { mSupportsAssist = array.getBoolean( com.android.internal.R.styleable.VoiceInteractionService_supportsAssist, false); + mSupportsLaunchFromKeyguard = array.getBoolean(com.android.internal. + R.styleable.VoiceInteractionService_supportsLaunchVoiceAssistFromKeyguard, + false); array.recycle(); if (mSessionService == null) { mParseError = "No sessionService specified"; @@ -148,4 +152,8 @@ public class VoiceInteractionServiceInfo { public boolean getSupportsAssist() { return mSupportsAssist; } + + public boolean getSupportsLaunchFromKeyguard() { + return mSupportsLaunchFromKeyguard; + } } diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index cf29310..3eeb04a 100644 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -1481,14 +1481,8 @@ public class TextToSpeech { // interface). // Sanitize locale using isLanguageAvailable. - int result = service.isLanguageAvailable( language, country, variant); - if (result >= LANG_AVAILABLE){ - if (result < LANG_COUNTRY_VAR_AVAILABLE) { - variant = ""; - if (result < LANG_COUNTRY_AVAILABLE) { - country = ""; - } - } + int result = service.isLanguageAvailable(language, country, variant); + if (result >= LANG_AVAILABLE) { // Get the default voice for the locale. String voiceName = service.getDefaultVoiceNameFor(language, country, variant); if (TextUtils.isEmpty(voiceName)) { @@ -1502,10 +1496,28 @@ public class TextToSpeech { return LANG_NOT_SUPPORTED; } + // Set the language/country/variant of the voice, so #getLanguage will return + // the currently set voice locale when called. + Voice voice = getVoice(service, voiceName); + String voiceLanguage = ""; + try { + voiceLanguage = voice.getLocale().getISO3Language(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + + voice.getLocale(), e); + } + + String voiceCountry = ""; + try { + voiceCountry = voice.getLocale().getISO3Country(); + } catch (MissingResourceException e) { + Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + + voice.getLocale(), e); + } mParams.putString(Engine.KEY_PARAM_VOICE_NAME, voiceName); - mParams.putString(Engine.KEY_PARAM_LANGUAGE, language); - mParams.putString(Engine.KEY_PARAM_COUNTRY, country); - mParams.putString(Engine.KEY_PARAM_VARIANT, variant); + mParams.putString(Engine.KEY_PARAM_LANGUAGE, voiceLanguage); + mParams.putString(Engine.KEY_PARAM_COUNTRY, voiceCountry); + mParams.putString(Engine.KEY_PARAM_VARIANT, voice.getLocale().getVariant()); } return result; } @@ -1654,20 +1666,32 @@ public class TextToSpeech { if (TextUtils.isEmpty(voiceName)) { return null; } - List<Voice> voices = service.getVoices(); - if (voices == null) { - return null; - } - for (Voice voice : voices) { - if (voice.getName().equals(voiceName)) { - return voice; - } - } - return null; + return getVoice(service, voiceName); } }, null, "getVoice"); } + + /** + * Returns a Voice instance of the voice with the given voice name. + * + * @return Voice instance with the given voice name, or {@code null} if not set or on error. + * + * @see Voice + */ + private Voice getVoice(ITextToSpeechService service, String voiceName) throws RemoteException { + List<Voice> voices = service.getVoices(); + if (voices == null) { + return null; + } + for (Voice voice : voices) { + if (voice.getName().equals(voiceName)) { + return voice; + } + } + return null; + } + /** * Returns a Voice instance that's the default voice for the default Text-to-speech language. * @return The default voice instance for the default language, or {@code null} if not set or @@ -1690,14 +1714,7 @@ public class TextToSpeech { // Sanitize the locale using isLanguageAvailable. int result = service.isLanguageAvailable(language, country, variant); - if (result >= LANG_AVAILABLE){ - if (result < LANG_COUNTRY_VAR_AVAILABLE) { - variant = ""; - if (result < LANG_COUNTRY_AVAILABLE) { - country = ""; - } - } - } else { + if (result < LANG_AVAILABLE) { // The default language is not supported. return null; } diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index 7bebbfb..a55a08c 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -278,7 +278,7 @@ public class Html { if (style[j] instanceof TypefaceSpan) { String s = ((TypefaceSpan) style[j]).getFamily(); - if (s.equals("monospace")) { + if ("monospace".equals(s)) { out.append("<tt>"); } } diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 451abea..59c7c6d 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -91,6 +91,7 @@ public class StaticLayout extends Layout { b.mEllipsizedWidth = width; b.mEllipsize = null; b.mMaxLines = Integer.MAX_VALUE; + b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; b.mMeasuredText = MeasuredText.obtain(); return b; @@ -100,6 +101,8 @@ public class StaticLayout extends Layout { b.mPaint = null; b.mText = null; MeasuredText.recycle(b.mMeasuredText); + b.mMeasuredText = null; + nFinishBuilder(b.mNativePtr); sPool.release(b); } diff --git a/core/java/android/text/method/WordIterator.java b/core/java/android/text/method/WordIterator.java index 11226a9..c4dc5ed 100644 --- a/core/java/android/text/method/WordIterator.java +++ b/core/java/android/text/method/WordIterator.java @@ -95,6 +95,45 @@ public class WordIterator implements Selection.PositionIterator { } while (true); } + /** {@inheritDoc} */ + public boolean isBoundary(int offset) { + int shiftedOffset = offset - mOffsetShift; + checkOffsetIsValid(shiftedOffset); + return mIterator.isBoundary(shiftedOffset); + } + + /** + * Returns the position of next boundary after the given offset. Returns + * {@code DONE} if there is no boundary after the given offset. + * + * @param offset the given start position to search from. + * @return the position of the last boundary preceding the given offset. + */ + public int nextBoundary(int offset) { + int shiftedOffset = offset - mOffsetShift; + shiftedOffset = mIterator.following(shiftedOffset); + if (shiftedOffset == BreakIterator.DONE) { + return BreakIterator.DONE; + } + return shiftedOffset + mOffsetShift; + } + + /** + * Returns the position of boundary preceding the given offset or + * {@code DONE} if the given offset specifies the starting position. + * + * @param offset the given start position to search from. + * @return the position of the last boundary preceding the given offset. + */ + public int prevBoundary(int offset) { + int shiftedOffset = offset - mOffsetShift; + shiftedOffset = mIterator.preceding(shiftedOffset); + if (shiftedOffset == BreakIterator.DONE) { + return BreakIterator.DONE; + } + return shiftedOffset + mOffsetShift; + } + /** If <code>offset</code> is within a word, returns the index of the first character of that * word, otherwise returns BreakIterator.DONE. * diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index ebc2aac..1b25505 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -1639,6 +1639,7 @@ public abstract class Transition implements Cloneable { for (int i = 0; i < count; i++) { TransitionValues values = lookIn.get(i); if (values == null) { + // Null values are always added to the end of the list, so we know to stop now. return null; } if (values.view == view) { @@ -1742,6 +1743,9 @@ public abstract class Transition implements Cloneable { View oldView = oldInfo.view; TransitionValues startValues = getTransitionValues(oldView, true); TransitionValues endValues = getMatchedTransitionValues(oldView, true); + if (startValues == null && endValues == null) { + endValues = mEndValues.viewValues.get(oldView); + } boolean cancel = (startValues != null || endValues != null) && oldInfo.transition.areValuesChanged(oldValues, endValues); if (cancel) { diff --git a/core/java/android/util/FloatMath.java b/core/java/android/util/FloatMath.java index 8f488af..bb7d15f 100644 --- a/core/java/android/util/FloatMath.java +++ b/core/java/android/util/FloatMath.java @@ -25,6 +25,8 @@ package android.util; * {@link java.lang.Math}. {@link java.lang.Math} should be used in * preference. * + * <p>All methods were removed from the public API in version 23. + * * @deprecated Use {@link java.lang.Math} instead. */ @Deprecated @@ -39,6 +41,7 @@ public class FloatMath { * * @param value to be converted * @return the floor of value + * @removed */ public static float floor(float value) { return (float) Math.floor(value); @@ -50,6 +53,7 @@ public class FloatMath { * * @param value to be converted * @return the ceiling of value + * @removed */ public static float ceil(float value) { return (float) Math.ceil(value); @@ -60,6 +64,7 @@ public class FloatMath { * * @param angle to compute the cosine of, in radians * @return the sine of angle + * @removed */ public static float sin(float angle) { return (float) Math.sin(angle); @@ -70,6 +75,7 @@ public class FloatMath { * * @param angle to compute the cosine of, in radians * @return the cosine of angle + * @removed */ public static float cos(float angle) { return (float) Math.cos(angle); @@ -81,6 +87,7 @@ public class FloatMath { * * @param value to compute sqrt of * @return the square root of value + * @removed */ public static float sqrt(float value) { return (float) Math.sqrt(value); @@ -92,6 +99,7 @@ public class FloatMath { * * @param value to compute the exponential of * @return the exponential of value + * @removed */ public static float exp(float value) { return (float) Math.exp(value); @@ -104,6 +112,7 @@ public class FloatMath { * @param x the base of the operation. * @param y the exponent of the operation. * @return {@code x} to the power of {@code y}. + * @removed */ public static float pow(float x, float y) { return (float) Math.pow(x, y); @@ -116,6 +125,7 @@ public class FloatMath { * @param x a float number * @param y a float number * @return the hypotenuse + * @removed */ public static float hypot(float x, float y) { return (float) Math.hypot(x, y); diff --git a/core/java/android/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java index 48167c8..bb761f0 100644 --- a/core/java/android/view/DisplayListCanvas.java +++ b/core/java/android/view/DisplayListCanvas.java @@ -93,12 +93,6 @@ public class DisplayListCanvas extends Canvas { private static native long nCreateDisplayListCanvas(); - public static void setProperty(String name, String value) { - nSetProperty(name, value); - } - - private static native void nSetProperty(String name, String value); - /////////////////////////////////////////////////////////////////////////// // Canvas management /////////////////////////////////////////////////////////////////////////// diff --git a/core/java/android/view/GhostView.java b/core/java/android/view/GhostView.java index bc38e1a..9f46f45 100644 --- a/core/java/android/view/GhostView.java +++ b/core/java/android/view/GhostView.java @@ -40,8 +40,7 @@ public class GhostView extends View { mView.mGhostView = this; final ViewGroup parent = (ViewGroup) mView.getParent(); setGhostedVisibility(View.INVISIBLE); - parent.mRecreateDisplayList = true; - parent.updateDisplayListIfDirty(); + parent.invalidate(); } @Override diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 91e6d68..1fd7109 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -17,6 +17,7 @@ package android.view; import android.annotation.IntDef; +import android.annotation.NonNull; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; @@ -412,6 +413,13 @@ public class ThreadedRenderer extends HardwareRenderer { nTrimMemory(level); } + public static void overrideProperty(@NonNull String name, @NonNull String value) { + if (name == null || value == null) { + throw new IllegalArgumentException("name and value must be non-null"); + } + nOverrideProperty(name, value); + } + public static void dumpProfileData(byte[] data, FileDescriptor fd) { nDumpProfileData(data, fd); } @@ -510,6 +518,7 @@ public class ThreadedRenderer extends HardwareRenderer { private static native void nDestroyHardwareResources(long nativeProxy); private static native void nTrimMemory(int level); + private static native void nOverrideProperty(String name, String value); private static native void nFence(long nativeProxy); private static native void nStopDrawing(long nativeProxy); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 75dc0a2..f62e6a2 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -2395,10 +2395,143 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int PFLAG3_NESTED_SCROLLING_ENABLED = 0x80; + /** + * Flag indicating that the bottom scroll indicator should be displayed + * when this view can scroll up. + */ + static final int PFLAG3_SCROLL_INDICATOR_TOP = 0x0100; + + /** + * Flag indicating that the bottom scroll indicator should be displayed + * when this view can scroll down. + */ + static final int PFLAG3_SCROLL_INDICATOR_BOTTOM = 0x0200; + + /** + * Flag indicating that the left scroll indicator should be displayed + * when this view can scroll left. + */ + static final int PFLAG3_SCROLL_INDICATOR_LEFT = 0x0400; + + /** + * Flag indicating that the right scroll indicator should be displayed + * when this view can scroll right. + */ + static final int PFLAG3_SCROLL_INDICATOR_RIGHT = 0x0800; + + /** + * Flag indicating that the start scroll indicator should be displayed + * when this view can scroll in the start direction. + */ + static final int PFLAG3_SCROLL_INDICATOR_START = 0x1000; + + /** + * Flag indicating that the end scroll indicator should be displayed + * when this view can scroll in the end direction. + */ + static final int PFLAG3_SCROLL_INDICATOR_END = 0x2000; + /* End of masks for mPrivateFlags3 */ static final int DRAG_MASK = PFLAG2_DRAG_CAN_ACCEPT | PFLAG2_DRAG_HOVERED; + static final int SCROLL_INDICATORS_NONE = 0x0000; + + /** + * Mask for use with setFlags indicating bits used for indicating which + * scroll indicators are enabled. + */ + static final int SCROLL_INDICATORS_PFLAG3_MASK = PFLAG3_SCROLL_INDICATOR_TOP + | PFLAG3_SCROLL_INDICATOR_BOTTOM | PFLAG3_SCROLL_INDICATOR_LEFT + | PFLAG3_SCROLL_INDICATOR_RIGHT | PFLAG3_SCROLL_INDICATOR_START + | PFLAG3_SCROLL_INDICATOR_END; + + /** + * Left-shift required to translate between public scroll indicator flags + * and internal PFLAGS3 flags. When used as a right-shift, translates + * PFLAGS3 flags to public flags. + */ + static final int SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT = 8; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, + value = { + SCROLL_INDICATOR_TOP, + SCROLL_INDICATOR_BOTTOM, + SCROLL_INDICATOR_LEFT, + SCROLL_INDICATOR_RIGHT, + SCROLL_INDICATOR_START, + SCROLL_INDICATOR_END, + }) + public @interface ScrollIndicators {} + + /** + * Scroll indicator direction for the top edge of the view. + * + * @see #setScrollIndicators(int) + * @see #setScrollIndicators(int, int) + * @see #getScrollIndicators() + */ + public static final int SCROLL_INDICATOR_TOP = + PFLAG3_SCROLL_INDICATOR_TOP >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + + /** + * Scroll indicator direction for the bottom edge of the view. + * + * @see #setScrollIndicators(int) + * @see #setScrollIndicators(int, int) + * @see #getScrollIndicators() + */ + public static final int SCROLL_INDICATOR_BOTTOM = + PFLAG3_SCROLL_INDICATOR_BOTTOM >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + + /** + * Scroll indicator direction for the left edge of the view. + * + * @see #setScrollIndicators(int) + * @see #setScrollIndicators(int, int) + * @see #getScrollIndicators() + */ + public static final int SCROLL_INDICATOR_LEFT = + PFLAG3_SCROLL_INDICATOR_LEFT >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + + /** + * Scroll indicator direction for the right edge of the view. + * + * @see #setScrollIndicators(int) + * @see #setScrollIndicators(int, int) + * @see #getScrollIndicators() + */ + public static final int SCROLL_INDICATOR_RIGHT = + PFLAG3_SCROLL_INDICATOR_RIGHT >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + + /** + * Scroll indicator direction for the starting edge of the view. + * <p> + * Resolved according to the view's layout direction, see + * {@link #getLayoutDirection()} for more information. + * + * @see #setScrollIndicators(int) + * @see #setScrollIndicators(int, int) + * @see #getScrollIndicators() + */ + public static final int SCROLL_INDICATOR_START = + PFLAG3_SCROLL_INDICATOR_START >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + + /** + * Scroll indicator direction for the ending edge of the view. + * <p> + * Resolved according to the view's layout direction, see + * {@link #getLayoutDirection()} for more information. + * + * @see #setScrollIndicators(int) + * @see #setScrollIndicators(int, int) + * @see #getScrollIndicators() + */ + public static final int SCROLL_INDICATOR_END = + PFLAG3_SCROLL_INDICATOR_END >> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + /** * <p>Indicates that we are allowing {@link android.view.ViewAssistStructure} to traverse * into this view.<p> @@ -3217,6 +3350,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @ViewDebug.ExportedProperty(deepExport = true, prefix = "fg_") private ForegroundInfo mForegroundInfo; + private Drawable mScrollIndicatorDrawable; + /** * RenderNode used for backgrounds. * <p> @@ -3769,6 +3904,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY; int overScrollMode = mOverScrollMode; boolean initializeScrollbars = false; + boolean initializeScrollIndicators = false; boolean startPaddingDefined = false; boolean endPaddingDefined = false; @@ -4135,6 +4271,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } mForegroundInfo.mInsidePadding = a.getBoolean(attr, mForegroundInfo.mInsidePadding); + case R.styleable.View_scrollIndicators: + final int scrollIndicators = + a.getInt(attr, SCROLL_INDICATORS_NONE) & SCROLL_INDICATORS_PFLAG3_MASK; + if (scrollIndicators != 0) { + viewFlagValues |= scrollIndicators; + viewFlagMasks |= SCROLL_INDICATORS_PFLAG3_MASK; + initializeScrollIndicators = true; + } break; } } @@ -4211,6 +4355,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, initializeScrollbarsInternal(a); } + if (initializeScrollIndicators) { + initializeScrollIndicatorsInternal(); + } + a.recycle(); // Needs to be called after mViewFlags is set @@ -4682,6 +4830,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback, resolvePadding(); } + private void initializeScrollIndicatorsInternal() { + // Some day maybe we'll break this into top/left/start/etc. and let the + // client control it. Until then, you can have any scroll indicator you + // want as long as it's a 1dp foreground-colored rectangle. + if (mScrollIndicatorDrawable == null) { + mScrollIndicatorDrawable = mContext.getDrawable(R.drawable.scroll_indicator_material); + } + } + /** * <p> * Initalizes the scrollability cache if necessary. @@ -4721,6 +4878,118 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return mVerticalScrollbarPosition; } + /** + * Sets the state of all scroll indicators. + * <p> + * See {@link #setScrollIndicators(int, int)} for usage information. + * + * @param indicators a bitmask of indicators that should be enabled, or + * {@code 0} to disable all indicators + * @see #setScrollIndicators(int, int) + * @see #getScrollIndicators() + * @attr ref android.R.styleable#View_scrollIndicators + */ + public void setScrollIndicators(@ScrollIndicators int indicators) { + setScrollIndicators(indicators, SCROLL_INDICATORS_PFLAG3_MASK); + } + + /** + * Sets the state of the scroll indicators specified by the mask. To change + * all scroll indicators at once, see {@link #setScrollIndicators(int)}. + * <p> + * When a scroll indicator is enabled, it will be displayed if the view + * can scroll in the direction of the indicator. + * <p> + * Multiple indicator types may be enabled or disabled by passing the + * logical OR of the desired types. If multiple types are specified, they + * will all be set to the same enabled state. + * <p> + * For example, to enable the top scroll indicatorExample: {@code setScrollIndicators + * + * @param indicators the indicator direction, or the logical OR of multiple + * indicator directions. One or more of: + * <ul> + * <li>{@link #SCROLL_INDICATOR_TOP}</li> + * <li>{@link #SCROLL_INDICATOR_BOTTOM}</li> + * <li>{@link #SCROLL_INDICATOR_LEFT}</li> + * <li>{@link #SCROLL_INDICATOR_RIGHT}</li> + * <li>{@link #SCROLL_INDICATOR_START}</li> + * <li>{@link #SCROLL_INDICATOR_END}</li> + * </ul> + * @see #setScrollIndicators(int) + * @see #getScrollIndicators() + * @attr ref android.R.styleable#View_scrollIndicators + */ + public void setScrollIndicators(@ScrollIndicators int indicators, @ScrollIndicators int mask) { + // Shift and sanitize mask. + mask <<= SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + mask &= SCROLL_INDICATORS_PFLAG3_MASK; + + // Shift and mask indicators. + indicators <<= SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + indicators &= mask; + + // Merge with non-masked flags. + final int updatedFlags = indicators | (mPrivateFlags3 & ~mask); + + if (mPrivateFlags3 != updatedFlags) { + mPrivateFlags3 = updatedFlags; + + if (indicators != 0) { + initializeScrollIndicatorsInternal(); + } + invalidate(); + } + } + + /** + * Returns a bitmask representing the enabled scroll indicators. + * <p> + * For example, if the top and left scroll indicators are enabled and all + * other indicators are disabled, the return value will be + * {@code View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_LEFT}. + * <p> + * To check whether the bottom scroll indicator is enabled, use the value + * of {@code (getScrollIndicators() & View.SCROLL_INDICATOR_BOTTOM) != 0}. + * + * @return a bitmask representing the enabled scroll indicators + */ + @ScrollIndicators + public int getScrollIndicators() { + return (mPrivateFlags3 & SCROLL_INDICATORS_PFLAG3_MASK) + >>> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + } + + /** + * Returns whether the specified scroll indicator is enabled. + * <p> + * Multiple indicator types may be queried by passing the logical OR of the + * desired types. If multiple types are specified, the return value + * represents whether they are all enabled. + * + * @param direction the indicator direction, or the logical OR of multiple + * indicator directions. One or more of: + * <ul> + * <li>{@link #SCROLL_INDICATOR_TOP}</li> + * <li>{@link #SCROLL_INDICATOR_BOTTOM}</li> + * <li>{@link #SCROLL_INDICATOR_LEFT}</li> + * <li>{@link #SCROLL_INDICATOR_RIGHT}</li> + * <li>{@link #SCROLL_INDICATOR_START}</li> + * <li>{@link #SCROLL_INDICATOR_END}</li> + * </ul> + * @return {@code true} if the specified indicator(s) are enabled, + * {@code false} otherwise + * @attr ref android.R.styleable#View_scrollIndicators + */ + public boolean isScrollIndicatorEnabled(int direction) { + // Shift and sanitize input. + direction <<= SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT; + direction &= SCROLL_INDICATORS_PFLAG3_MASK; + + // All of the flags must be set. + return (mPrivateFlags3 & direction) == direction; + } + ListenerInfo getListenerInfo() { if (mListenerInfo != null) { return mListenerInfo; @@ -13444,6 +13713,75 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + void getScrollIndicatorBounds(@NonNull Rect out) { + out.left = mScrollX; + out.right = mScrollX + mRight - mLeft; + out.top = mScrollY; + out.bottom = mScrollY + mBottom - mTop; + } + + private void onDrawScrollIndicators(Canvas c) { + if ((mPrivateFlags3 & SCROLL_INDICATORS_PFLAG3_MASK) == 0) { + // No scroll indicators enabled. + return; + } + + final Drawable dr = mScrollIndicatorDrawable; + if (dr == null) { + // Scroll indicators aren't supported here. + return; + } + + final int h = dr.getIntrinsicHeight(); + final int w = dr.getIntrinsicWidth(); + final Rect rect = mAttachInfo.mTmpInvalRect; + getScrollIndicatorBounds(rect); + + if ((mPrivateFlags3 & PFLAG3_SCROLL_INDICATOR_TOP) != 0) { + final boolean canScrollUp = canScrollVertically(-1); + if (canScrollUp) { + dr.setBounds(rect.left, rect.top, rect.right, rect.top + h); + dr.draw(c); + } + } + + if ((mPrivateFlags3 & PFLAG3_SCROLL_INDICATOR_BOTTOM) != 0) { + final boolean canScrollDown = canScrollVertically(1); + if (canScrollDown) { + dr.setBounds(rect.left, rect.bottom - h, rect.right, rect.bottom); + dr.draw(c); + } + } + + final int leftRtl; + final int rightRtl; + if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) { + leftRtl = PFLAG3_SCROLL_INDICATOR_END; + rightRtl = PFLAG3_SCROLL_INDICATOR_START; + } else { + leftRtl = PFLAG3_SCROLL_INDICATOR_START; + rightRtl = PFLAG3_SCROLL_INDICATOR_END; + } + + final int leftMask = PFLAG3_SCROLL_INDICATOR_LEFT | leftRtl; + if ((mPrivateFlags3 & leftMask) != 0) { + final boolean canScrollLeft = canScrollHorizontally(-1); + if (canScrollLeft) { + dr.setBounds(rect.left, rect.top, rect.left + w, rect.bottom); + dr.draw(c); + } + } + + final int rightMask = PFLAG3_SCROLL_INDICATOR_RIGHT | rightRtl; + if ((mPrivateFlags3 & rightMask) != 0) { + final boolean canScrollRight = canScrollHorizontally(1); + if (canScrollRight) { + dr.setBounds(rect.right - w, rect.top, rect.right, rect.bottom); + dr.draw(c); + } + } + } + /** * <p>Request the drawing of the horizontal and the vertical scrollbar. The * scrollbars are painted only if they have been awakened first.</p> @@ -17272,6 +17610,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @param canvas canvas to draw into */ public void onDrawForeground(Canvas canvas) { + onDrawScrollIndicators(canvas); onDrawScrollBars(canvas); final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null; diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index f240fd6..babb4e9 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -18,6 +18,7 @@ package android.view; import android.animation.LayoutTransition; import android.annotation.IdRes; +import android.annotation.NonNull; import android.annotation.UiThread; import android.content.Context; import android.content.Intent; @@ -3547,6 +3548,21 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return child.draw(canvas, this, drawingTime); } + @Override + void getScrollIndicatorBounds(@NonNull Rect out) { + super.getScrollIndicatorBounds(out); + + // If we have padding and we're supposed to clip children to that + // padding, offset the scroll indicators to match our clip bounds. + final boolean clipToPadding = (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK; + if (clipToPadding) { + out.left += mPaddingLeft; + out.right -= mPaddingRight; + out.top += mPaddingTop; + out.bottom -= mPaddingBottom; + } + } + /** * Returns whether this group's children are clipped to their bounds before drawing. * The default value is true. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index fda6e63..c9c2a82 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -76,6 +76,7 @@ import android.widget.Scroller; import com.android.internal.R; import com.android.internal.os.SomeArgs; +import com.android.internal.policy.PhoneFallbackEventHandler; import com.android.internal.util.ScreenShapeHelper; import com.android.internal.view.BaseSurfaceHolder; import com.android.internal.view.RootViewSurfaceTaker; diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 78604bf..040fd37 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -247,6 +247,13 @@ public final class InputMethodManager { /** @hide */ public static final int DISPATCH_HANDLED = 1; + /** @hide */ + public static final int SHOW_IM_PICKER_MODE_AUTO = 0; + /** @hide */ + public static final int SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES = 1; + /** @hide */ + public static final int SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES = 2; + final IInputMethodManager mService; final Looper mMainLooper; @@ -1890,9 +1897,28 @@ public final class InputMethodManager { } } + /** + * Shows the input method chooser dialog. + * + * @param showAuxiliarySubtypes Set true to show auxiliary input methods. + * @hide + */ + public void showInputMethodPicker(boolean showAuxiliarySubtypes) { + synchronized (mH) { + try { + final int mode = showAuxiliarySubtypes ? + SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES: + SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES; + mService.showInputMethodPickerFromClient(mClient, mode); + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId, e); + } + } + } + private void showInputMethodPickerLocked() { try { - mService.showInputMethodPickerFromClient(mClient); + mService.showInputMethodPickerFromClient(mClient, SHOW_IM_PICKER_MODE_AUTO); } catch (RemoteException e) { Log.w(TAG, "IME died: " + mCurId, e); } diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java index e0b0e1f..f08141c 100644 --- a/core/java/android/widget/ActionMenuPresenter.java +++ b/core/java/android/widget/ActionMenuPresenter.java @@ -61,6 +61,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter implements ActionProvider.SubUiVisibilityListener { private static final String TAG = "ActionMenuPresenter"; private static final int ITEM_ANIMATION_DURATION = 150; + private static final boolean ACTIONBAR_ANIMATIONS_ENABLED = false; private OverflowMenuButton mOverflowButton; private boolean mReserveOverflow; @@ -414,7 +415,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter @Override public void updateMenuView(boolean cleared) { final ViewGroup menuViewParent = (ViewGroup) ((View) mMenuView).getParent(); - if (menuViewParent != null) { + if (menuViewParent != null && ACTIONBAR_ANIMATIONS_ENABLED) { setupItemAnimations(); } super.updateMenuView(cleared); diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 8d35b83..86a100f 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -688,44 +688,101 @@ public class Editor { private int getWordStart(int offset) { // FIXME - For this and similar methods we're not doing anything to check if there's // a LocaleSpan in the text, this may be something we should try handling or checking for. - int retOffset = getWordIteratorWithText().getBeginning(offset); - if (retOffset == BreakIterator.DONE) retOffset = offset; + int retOffset = getWordIteratorWithText().prevBoundary(offset); + if (isPunctBoundaryBehind(retOffset, true /* isStart */)) { + // If we're on a punctuation boundary we should continue to get the + // previous offset until we're not longer on a punctuation boundary. + retOffset = getWordIteratorWithText().prevBoundary(retOffset); + while (!isPunctBoundaryBehind(retOffset, false /* isStart */) + && retOffset != BreakIterator.DONE) { + retOffset = getWordIteratorWithText().prevBoundary(retOffset); + } + } + if (retOffset == BreakIterator.DONE) { + return offset; + } return retOffset; } - private int getWordEnd(int offset, boolean includePunctuation) { - int retOffset = getWordIteratorWithText().getEnd(offset); + private int getWordEnd(int offset) { + int retOffset = getWordIteratorWithText().nextBoundary(offset); + if (isPunctBoundaryForward(retOffset, true /* isStart */)) { + // If we're on a punctuation boundary we should continue to get the + // next offset until we're no longer on a punctuation boundary. + retOffset = getWordIteratorWithText().nextBoundary(retOffset); + while (!isPunctBoundaryForward(retOffset, false /* isStart */) + && retOffset != BreakIterator.DONE) { + retOffset = getWordIteratorWithText().nextBoundary(retOffset); + } + } if (retOffset == BreakIterator.DONE) { - retOffset = offset; - } else if (includePunctuation) { - retOffset = handlePunctuation(retOffset); + return offset; } return retOffset; } - private boolean isEndBoundary(int offset) { - int thisEnd = getWordEnd(offset, false); - return offset == thisEnd; - } + /** + * Checks for punctuation boundaries for the provided offset and the + * previous character. + * + * @param offset The offset to check from. + * @param isStart Whether the boundary being checked for is at the start or + * end of a punctuation sequence. + * @return Whether this is a punctuation boundary. + */ + private boolean isPunctBoundaryBehind(int offset, boolean isStart) { + CharSequence text = mTextView.getText(); + if (offset == BreakIterator.DONE || offset > text.length() || offset == 0) { + return false; + } + int cp = Character.codePointAt(text, offset); + int prevCp = Character.codePointBefore(text, offset); - private boolean isStartBoundary(int offset) { - int thisStart = getWordStart(offset); - return thisStart == offset; + if (isPunctuation(cp)) { + // If it's the start, the current cp and the prev cp are + // punctuation. If it's at the end of a punctuation sequence the + // current is punctuation and the prev is not. + return isStart ? isPunctuation(prevCp) : !isPunctuation(prevCp); + } + return false; } - private int handlePunctuation(int offset) { - // FIXME - Check with UX how repeated ending punctuation should be handled. - // FIXME - Check with UX if / how we would handle non sentence ending characters. - // FIXME - Consider punctuation in different languages. + /** + * Checks for punctuation boundaries for the provided offset and the next + * character. + * + * @param offset The offset to check from. + * @param isStart Whether the boundary being checked for is at the start or + * end of a punctuation sequence. + * @return Whether this is a punctuation boundary. + */ + private boolean isPunctBoundaryForward(int offset, boolean isStart) { CharSequence text = mTextView.getText(); - if (offset < text.length()) { - int c = Character.codePointAt(text, offset); - if (c == 0x002e /* period */|| c == 0x003f /* question mark */ - || c == 0x0021 /* exclamation mark */) { - offset = Character.offsetByCodePoints(text, offset, 1); - } + if (offset == BreakIterator.DONE || offset > text.length() || offset == 0) { + return false; + } + int cp = Character.codePointBefore(text, offset); + int nextCpOffset = Math.min(offset + Character.charCount(cp), text.length() - 1); + int nextCp = Character.codePointBefore(text, nextCpOffset); + + if (isPunctuation(cp)) { + // If it's the start, the current cp and the next cp are + // punctuation. If it's at the end of a punctuation sequence the + // current is punctuation and the next is not. + return isStart ? isPunctuation(nextCp) : !isPunctuation(nextCp); } - return offset; + return false; + } + + private boolean isPunctuation(int cp) { + int type = Character.getType(cp); + return (type == Character.CONNECTOR_PUNCTUATION || + type == Character.DASH_PUNCTUATION || + type == Character.END_PUNCTUATION || + type == Character.FINAL_QUOTE_PUNCTUATION || + type == Character.INITIAL_QUOTE_PUNCTUATION || + type == Character.OTHER_PUNCTUATION || + type == Character.START_PUNCTUATION); } /** @@ -788,7 +845,7 @@ public class Editor { if (selectionStart == BreakIterator.DONE || selectionEnd == BreakIterator.DONE || selectionStart == selectionEnd) { // Possible when the word iterator does not properly handle the text's language - long range = getCharRange(minOffset); + long range = getCharClusterRange(minOffset); selectionStart = TextUtils.unpackRangeStartFromLong(range); selectionEnd = TextUtils.unpackRangeEndFromLong(range); } @@ -831,29 +888,25 @@ public class Editor { return mWordIteratorWithText; } - private long getCharRange(int offset) { + private int getNextCursorOffset(int offset, boolean findAfterGivenOffset) { + final Layout layout = mTextView.getLayout(); + if (layout == null) return offset; + final CharSequence text = mTextView.getText(); + final int nextOffset = layout.getPaint().getTextRunCursor(text, 0, text.length(), + layout.isRtlCharAt(offset) ? Paint.DIRECTION_RTL : Paint.DIRECTION_LTR, + offset, findAfterGivenOffset ? Paint.CURSOR_AFTER : Paint.CURSOR_BEFORE); + return nextOffset == -1 ? offset : nextOffset; + } + + private long getCharClusterRange(int offset) { final int textLength = mTextView.getText().length(); - if (offset + 1 < textLength) { - final char currentChar = mTextView.getText().charAt(offset); - final char nextChar = mTextView.getText().charAt(offset + 1); - if (Character.isSurrogatePair(currentChar, nextChar)) { - return TextUtils.packRangeInLong(offset, offset + 2); - } - } if (offset < textLength) { - return TextUtils.packRangeInLong(offset, offset + 1); - } - if (offset - 2 >= 0) { - final char previousChar = mTextView.getText().charAt(offset - 1); - final char previousPreviousChar = mTextView.getText().charAt(offset - 2); - if (Character.isSurrogatePair(previousPreviousChar, previousChar)) { - return TextUtils.packRangeInLong(offset - 2, offset); - } + return TextUtils.packRangeInLong(offset, getNextCursorOffset(offset, true)); } if (offset - 1 >= 0) { - return TextUtils.packRangeInLong(offset - 1, offset); + return TextUtils.packRangeInLong(getNextCursorOffset(offset, false), offset); } - return TextUtils.packRangeInLong(offset, offset); + return TextUtils.packRangeInLong(offset, offset); } private boolean touchPositionIsInSelection() { @@ -2421,7 +2474,8 @@ public class Editor { public PinnedPopupWindow() { createPopupWindow(); - mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL); + mPopupWindow.setWindowLayoutType( + WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL); mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT); mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); @@ -3902,13 +3956,9 @@ public class Editor { public void updatePosition(float x, float y) { final int trueOffset = mTextView.getOffsetForPosition(x, y); final int currLine = mTextView.getLineAtCoordinate(y); - - // Don't select white space on different lines. - if (isWhitespaceLine(mPrevLine, currLine, trueOffset)) return; - boolean positionCursor = false; int offset = trueOffset; - int end = getWordEnd(offset, true); + int end = getWordEnd(offset); int start = getWordStart(offset); if (offset < mPreviousOffset) { @@ -3924,7 +3974,7 @@ public class Editor { } } mTouchWordOffset = Math.max(trueOffset - offset, 0); - mInWord = !isStartBoundary(offset); + mInWord = !getWordIteratorWithText().isBoundary(offset); positionCursor = true; } else if (offset - mTouchWordOffset > mPreviousOffset) { // User is shrinking the selection. @@ -3933,7 +3983,7 @@ public class Editor { offset = end; } offset -= mTouchWordOffset; - mInWord = !isEndBoundary(offset); + mInWord = !getWordIteratorWithText().isBoundary(offset); positionCursor = true; } @@ -3945,7 +3995,7 @@ public class Editor { int alteredOffset = mTextView.getOffsetAtCoordinate(mPrevLine, x); if (alteredOffset >= selectionEnd) { // Can't pass the other drag handle. - offset = Math.max(0, selectionEnd - 1); + offset = getNextCursorOffset(selectionEnd, false); } else { offset = alteredOffset; } @@ -4004,14 +4054,9 @@ public class Editor { public void updatePosition(float x, float y) { final int trueOffset = mTextView.getOffsetForPosition(x, y); final int currLine = mTextView.getLineAtCoordinate(y); - - // Don't select white space on different lines. - if (isWhitespaceLine(mPrevLine, currLine, trueOffset)) return; - int offset = trueOffset; boolean positionCursor = false; - - int end = getWordEnd(offset, true); + int end = getWordEnd(offset); int start = getWordStart(offset); if (offset > mPreviousOffset) { @@ -4027,7 +4072,7 @@ public class Editor { } } mTouchWordOffset = Math.max(offset - trueOffset, 0); - mInWord = !isEndBoundary(offset); + mInWord = !getWordIteratorWithText().isBoundary(offset); positionCursor = true; } else if (offset + mTouchWordOffset < mPreviousOffset) { // User is shrinking the selection. @@ -4037,7 +4082,7 @@ public class Editor { } offset += mTouchWordOffset; positionCursor = true; - mInWord = !isStartBoundary(offset); + mInWord = !getWordIteratorWithText().isBoundary(offset); } if (positionCursor) { @@ -4048,7 +4093,7 @@ public class Editor { int length = mTextView.getText().length(); if (alteredOffset <= selectionStart) { // Can't pass the other drag handle. - offset = Math.min(selectionStart + 1, length); + offset = getNextCursorOffset(selectionStart, true); } else { offset = Math.min(alteredOffset, length); } @@ -4069,36 +4114,6 @@ public class Editor { } /** - * Checks whether selection is happening on a different line than previous and - * if that line only contains whitespace up to the touch location. - * - * @param prevLine The previous line the selection was on. - * @param currLine The current line being selected. - * @param offset The offset in the text where the touch occurred. - * @return Whether or not it was just a white space line being selected. - */ - private boolean isWhitespaceLine(int prevLine, int currLine, int offset) { - if (prevLine == currLine) { - // Same line; don't care. - return false; - } - CharSequence text = mTextView.getText(); - if (offset == text.length()) { - // No character at the last position. - return false; - } - int lineEndOffset = mTextView.getLayout().getLineEnd(currLine); - for (int cp, i = offset; i < lineEndOffset; i += Character.charCount(cp)) { - cp = Character.codePointAt(text, i); - if (!Character.isSpaceChar(cp) && !Character.isWhitespace(cp)) { - // There are non white space chars on the line. - return false; - } - } - return true; - } - - /** * A CursorController instance can be used to control a cursor in the text. */ private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener { @@ -4177,8 +4192,6 @@ public class Editor { private int mStartOffset = -1; // Indicates whether the user is selecting text and using the drag accelerator. private boolean mDragAcceleratorActive; - // Indicates the line of text the drag accelerator is on. - private int mPrevLine = -1; SelectionModifierCursorController() { resetTouchOffsets(); @@ -4271,8 +4284,6 @@ public class Editor { } } - // New selection, reset line. - mPrevLine = mTextView.getLineAtCoordinate(y); mDownPositionX = x; mDownPositionY = y; mGestureStayedInTapRegion = true; @@ -4317,7 +4328,7 @@ public class Editor { // We don't start "dragging" until the user is past the initial word that // gets selected on long press. int firstWordStart = getWordStart(mStartOffset); - int firstWordEnd = getWordEnd(mStartOffset, false); + int firstWordEnd = getWordEnd(mStartOffset); if (offset > firstWordEnd || offset < firstWordStart) { // Basically the goal in the below code is to have the highlight be @@ -4329,13 +4340,6 @@ public class Editor { if (my > fingerOffset) my -= fingerOffset; offset = mTextView.getOffsetForPosition(mx, my); - int currLine = mTextView.getLineAtCoordinate(my); - - // Don't select white space on different lines. - if (isWhitespaceLine(mPrevLine, currLine, offset)) return; - - mPrevLine = currLine; - // Perform the check for closeness at edge of view, if we're very close // don't adjust the offset to be in front of the finger - otherwise the // user can't select words at the edge. @@ -4364,7 +4368,7 @@ public class Editor { // Need to adjust start offset based on direction of movement. int newStart = mStartOffset < offset ? getWordStart(mStartOffset) - : getWordEnd(mStartOffset, true); + : getWordEnd(mStartOffset); Selection.setSelection((Spannable) mTextView.getText(), newStart, offset); } diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java index 310412f..94b9416 100644 --- a/core/java/android/widget/ListPopupWindow.java +++ b/core/java/android/widget/ListPopupWindow.java @@ -39,6 +39,7 @@ import android.view.View.OnTouchListener; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; +import android.view.WindowManager; import android.view.animation.AccelerateDecelerateInterpolator; import com.android.internal.R; @@ -77,6 +78,7 @@ public class ListPopupWindow { private int mDropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT; private int mDropDownHorizontalOffset; private int mDropDownVerticalOffset; + private int mDropDownWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL; private boolean mDropDownVerticalOffsetSet; private int mDropDownGravity = Gravity.NO_GRAVITY; @@ -515,6 +517,19 @@ public class ListPopupWindow { } /** + * Set the layout type for this popup window. + * <p> + * See {@link WindowManager.LayoutParams#type} for possible values. + * + * @param layoutType Layout type for this window. + * + * @see WindowManager.LayoutParams#type + */ + public void setWindowLayoutType(int layoutType) { + mDropDownWindowLayoutType = layoutType; + } + + /** * Sets a listener to receive events when a list item is clicked. * * @param clickListener Listener to register @@ -567,8 +582,9 @@ public class ListPopupWindow { public void show() { int height = buildDropDown(); - boolean noInputMethod = isInputMethodNotNeeded(); + final boolean noInputMethod = isInputMethodNotNeeded(); mPopup.setAllowScrollingAnchorParent(!noInputMethod); + mPopup.setWindowLayoutType(mDropDownWindowLayoutType); if (mPopup.isShowing()) { final int widthSpec; @@ -1775,8 +1791,9 @@ public class ListPopupWindow { private class ResizePopupRunnable implements Runnable { public void run() { - if (mDropDownList != null && mDropDownList.getCount() > mDropDownList.getChildCount() && - mDropDownList.getChildCount() <= mListItemExpandMaximum) { + if (mDropDownList != null && mDropDownList.isAttachedToWindow() + && mDropDownList.getCount() > mDropDownList.getChildCount() + && mDropDownList.getChildCount() <= mListItemExpandMaximum) { mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); show(); } diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java index 8d8b3a3..97348e30 100644 --- a/core/java/android/widget/MediaController.java +++ b/core/java/android/widget/MediaController.java @@ -28,7 +28,7 @@ import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.PhoneWindow; +import com.android.internal.policy.PhoneWindow; import android.view.View; import android.view.ViewGroup; import android.view.Window; diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index c3ac278..b4cbf35 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -829,9 +829,9 @@ public class PopupWindow { } /** - * Set the layout type for this window. This value will be passed through to - * {@link WindowManager.LayoutParams#type} therefore the value should match any value - * {@link WindowManager.LayoutParams#type} accepts. + * Set the layout type for this window. + * <p> + * See {@link WindowManager.LayoutParams#type} for possible values. * * @param layoutType Layout type for this window. * diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java index b8110e3..61ee00c 100644 --- a/core/java/com/android/internal/app/AlertController.java +++ b/core/java/com/android/internal/app/AlertController.java @@ -526,11 +526,15 @@ public class AlertController { mWindow.setCloseOnTouchOutsideIfNotSet(true); } - // Only display the divider if we have a title and a custom view or a - // message. if (hasTopPanel) { + // Only clip scrolling content to padding if we have a title. + if (mScrollView != null) { + mScrollView.setClipToPadding(true); + } + + // Only show the divider if we have a title. final View divider; - if (mMessage != null || hasCustomPanel || mListView != null) { + if (mMessage != null || mListView != null || hasCustomPanel) { divider = topPanel.findViewById(R.id.titleDivider); } else { divider = topPanel.findViewById(R.id.titleDividerTop); @@ -541,6 +545,17 @@ public class AlertController { } } + // Update scroll indicators as needed. + if (!hasCustomPanel) { + final View content = mListView != null ? mListView : mScrollView; + if (content != null) { + final int indicators = (hasTopPanel ? View.SCROLL_INDICATOR_TOP : 0) + | (hasButtonPanel ? View.SCROLL_INDICATOR_BOTTOM : 0); + content.setScrollIndicators(indicators, + View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM); + } + } + final TypedArray a = mContext.obtainStyledAttributes( null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0); setBackground(a, topPanel, contentPanel, customPanel, buttonPanel, @@ -654,59 +669,6 @@ public class AlertController { contentPanel.setVisibility(View.GONE); } } - - // Set up scroll indicators (if present). - final View indicatorUp = contentPanel.findViewById(R.id.scrollIndicatorUp); - final View indicatorDown = contentPanel.findViewById(R.id.scrollIndicatorDown); - if (indicatorUp != null || indicatorDown != null) { - if (mMessage != null) { - // We're just showing the ScrollView, set up listener. - mScrollView.setOnScrollChangeListener(new View.OnScrollChangeListener() { - @Override - public void onScrollChange(View v, int scrollX, int scrollY, - int oldScrollX, int oldScrollY) { - manageScrollIndicators(v, indicatorUp, indicatorDown); - } - }); - // Set up the indicators following layout. - mScrollView.post(new Runnable() { - @Override - public void run() { - manageScrollIndicators(mScrollView, indicatorUp, indicatorDown); - } - }); - - } else if (mListView != null) { - // We're just showing the AbsListView, set up listener. - mListView.setOnScrollListener(new AbsListView.OnScrollListener() { - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - // That's cool, I guess? - } - - @Override - public void onScroll(AbsListView v, int firstVisibleItem, - int visibleItemCount, int totalItemCount) { - manageScrollIndicators(v, indicatorUp, indicatorDown); - } - }); - // Set up the indicators following layout. - mListView.post(new Runnable() { - @Override - public void run() { - manageScrollIndicators(mListView, indicatorUp, indicatorDown); - } - }); - } else { - // We don't have any content to scroll, remove the indicators. - if (indicatorUp != null) { - contentPanel.removeView(indicatorUp); - } - if (indicatorDown != null) { - contentPanel.removeView(indicatorDown); - } - } - } } private static void manageScrollIndicators(View v, View upIndicator, View downIndicator) { diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index e347faa..62ca1f0 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -21,6 +21,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender; +import android.content.IntentSender.SendIntentException; import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; @@ -34,6 +35,7 @@ import android.os.IBinder; import android.os.Message; import android.os.Parcelable; import android.os.RemoteException; +import android.os.ResultReceiver; import android.os.UserHandle; import android.service.chooser.ChooserTarget; import android.service.chooser.ChooserTargetService; @@ -53,11 +55,13 @@ public class ChooserActivity extends ResolverActivity { private static final boolean DEBUG = false; - private static final int QUERY_TARGET_LIMIT = 5; + private static final int QUERY_TARGET_SERVICE_LIMIT = 5; private static final int WATCHDOG_TIMEOUT_MILLIS = 5000; private Bundle mReplacementExtras; private IntentSender mChosenComponentSender; + private IntentSender mRefinementIntentSender; + private RefinementResultReceiver mRefinementResultReceiver; private ChooserTarget[] mCallerChooserTargets; @@ -113,6 +117,32 @@ public class ChooserActivity extends ResolverActivity { if (target != null) { modifyTargetIntent(target); } + Parcelable[] targetsParcelable + = intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS); + if (targetsParcelable != null) { + final boolean offset = target == null; + Intent[] additionalTargets = + new Intent[offset ? targetsParcelable.length - 1 : targetsParcelable.length]; + for (int i = 0; i < targetsParcelable.length; i++) { + if (!(targetsParcelable[i] instanceof Intent)) { + Log.w(TAG, "EXTRA_ALTERNATE_INTENTS array entry #" + i + " is not an Intent: " + + targetsParcelable[i]); + finish(); + super.onCreate(null); + return; + } + final Intent additionalTarget = (Intent) targetsParcelable[i]; + if (i == 0 && target == null) { + target = additionalTarget; + modifyTargetIntent(target); + } else { + additionalTargets[offset ? i - 1 : i] = additionalTarget; + modifyTargetIntent(additionalTarget); + } + } + setAdditionalTargets(additionalTargets); + } + mReplacementExtras = intent.getBundleExtra(Intent.EXTRA_REPLACEMENT_EXTRAS); CharSequence title = intent.getCharSequenceExtra(Intent.EXTRA_TITLE); int defaultTitleRes = 0; @@ -125,7 +155,7 @@ public class ChooserActivity extends ResolverActivity { initialIntents = new Intent[pa.length]; for (int i=0; i<pa.length; i++) { if (!(pa[i] instanceof Intent)) { - Log.w("ChooserActivity", "Initial intent #" + i + " not an Intent: " + pa[i]); + Log.w(TAG, "Initial intent #" + i + " not an Intent: " + pa[i]); finish(); super.onCreate(null); return; @@ -141,8 +171,7 @@ public class ChooserActivity extends ResolverActivity { final ChooserTarget[] targets = new ChooserTarget[pa.length]; for (int i = 0; i < pa.length; i++) { if (!(pa[i] instanceof ChooserTarget)) { - Log.w("ChooserActivity", "Chooser target #" + i + " is not a ChooserTarget: " + - pa[i]); + Log.w(TAG, "Chooser target #" + i + " is not a ChooserTarget: " + pa[i]); finish(); super.onCreate(null); return; @@ -153,12 +182,23 @@ public class ChooserActivity extends ResolverActivity { } mChosenComponentSender = intent.getParcelableExtra( Intent.EXTRA_CHOSEN_COMPONENT_INTENT_SENDER); + mRefinementIntentSender = intent.getParcelableExtra( + Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER); setSafeForwardingMode(true); super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents, null, false); } @Override + protected void onDestroy() { + super.onDestroy(); + if (mRefinementResultReceiver != null) { + mRefinementResultReceiver.destroy(); + mRefinementResultReceiver = null; + } + } + + @Override public Intent getReplacementIntent(ActivityInfo aInfo, Intent defIntent) { Intent result = defIntent; if (mReplacementExtras != null) { @@ -211,6 +251,37 @@ public class ChooserActivity extends ResolverActivity { } } + @Override + protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) { + if (mRefinementIntentSender != null) { + final Intent fillIn = new Intent(); + final List<Intent> sourceIntents = target.getAllSourceIntents(); + if (!sourceIntents.isEmpty()) { + fillIn.putExtra(Intent.EXTRA_INTENT, sourceIntents.get(0)); + if (sourceIntents.size() > 1) { + final Intent[] alts = new Intent[sourceIntents.size() - 1]; + for (int i = 1, N = sourceIntents.size(); i < N; i++) { + alts[i - 1] = sourceIntents.get(i); + } + fillIn.putExtra(Intent.EXTRA_ALTERNATE_INTENTS, alts); + } + if (mRefinementResultReceiver != null) { + mRefinementResultReceiver.destroy(); + } + mRefinementResultReceiver = new RefinementResultReceiver(this, target, null); + fillIn.putExtra(Intent.EXTRA_RESULT_RECEIVER, + mRefinementResultReceiver); + try { + mRefinementIntentSender.sendIntent(this, 0, fillIn, null, null); + return false; + } catch (SendIntentException e) { + Log.e(TAG, "Refinement IntentSender failed to send", e); + } + } + } + return super.onTargetSelected(target, alwaysCheck); + } + void queryTargetServices(ChooserListAdapter adapter) { final PackageManager pm = getPackageManager(); int targetsToQuery = 0; @@ -258,8 +329,9 @@ public class ChooserActivity extends ResolverActivity { targetsToQuery++; } } - if (targetsToQuery >= QUERY_TARGET_LIMIT) { - if (DEBUG) Log.d(TAG, "queryTargets hit query target limit " + QUERY_TARGET_LIMIT); + if (targetsToQuery >= QUERY_TARGET_SERVICE_LIMIT) { + if (DEBUG) Log.d(TAG, "queryTargets hit query target limit " + + QUERY_TARGET_SERVICE_LIMIT); break; } } @@ -303,6 +375,43 @@ public class ChooserActivity extends ResolverActivity { mTargetResultHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT); } + void onRefinementResult(TargetInfo selectedTarget, Intent matchingIntent) { + if (mRefinementResultReceiver != null) { + mRefinementResultReceiver.destroy(); + mRefinementResultReceiver = null; + } + + if (selectedTarget == null) { + Log.e(TAG, "Refinement result intent did not match any known targets; canceling"); + } else if (!checkTargetSourceIntent(selectedTarget, matchingIntent)) { + Log.e(TAG, "onRefinementResult: Selected target " + selectedTarget + + " cannot match refined source intent " + matchingIntent); + } else if (super.onTargetSelected(selectedTarget.cloneFilledIn(matchingIntent, 0), false)) { + finish(); + return; + } + onRefinementCanceled(); + } + + void onRefinementCanceled() { + if (mRefinementResultReceiver != null) { + mRefinementResultReceiver.destroy(); + mRefinementResultReceiver = null; + } + finish(); + } + + boolean checkTargetSourceIntent(TargetInfo target, Intent matchingIntent) { + final List<Intent> targetIntents = target.getAllSourceIntents(); + for (int i = 0, N = targetIntents.size(); i < N; i++) { + final Intent targetIntent = targetIntents.get(i); + if (targetIntent.filterEquals(matchingIntent)) { + return true; + } + } + return false; + } + @Override ResolveListAdapter createAdapter(Context context, Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid, boolean filterLastUsed) { @@ -313,17 +422,19 @@ public class ChooserActivity extends ResolverActivity { return adapter; } - class ChooserTargetInfo implements TargetInfo { - private final TargetInfo mSourceInfo; + final class ChooserTargetInfo implements TargetInfo { + private final DisplayResolveInfo mSourceInfo; private final ResolveInfo mBackupResolveInfo; private final ChooserTarget mChooserTarget; private final Drawable mDisplayIcon; + private final Intent mFillInIntent; + private final int mFillInFlags; public ChooserTargetInfo(ChooserTarget target) { this(null, target); } - public ChooserTargetInfo(TargetInfo sourceInfo, ChooserTarget chooserTarget) { + public ChooserTargetInfo(DisplayResolveInfo sourceInfo, ChooserTarget chooserTarget) { mSourceInfo = sourceInfo; mChooserTarget = chooserTarget; mDisplayIcon = new BitmapDrawable(getResources(), chooserTarget.getIcon()); @@ -333,6 +444,18 @@ public class ChooserActivity extends ResolverActivity { } else { mBackupResolveInfo = getPackageManager().resolveActivity(getResolvedIntent(), 0); } + + mFillInIntent = null; + mFillInFlags = 0; + } + + private ChooserTargetInfo(ChooserTargetInfo other, Intent fillInIntent, int flags) { + mSourceInfo = other.mSourceInfo; + mBackupResolveInfo = other.mBackupResolveInfo; + mChooserTarget = other.mChooserTarget; + mDisplayIcon = other.mDisplayIcon; + mFillInIntent = fillInIntent; + mFillInFlags = flags; } @Override @@ -358,22 +481,42 @@ public class ChooserActivity extends ResolverActivity { } private Intent getFillInIntent() { - return mSourceInfo != null ? mSourceInfo.getResolvedIntent() : getTargetIntent(); + Intent result = mSourceInfo != null + ? mSourceInfo.getResolvedIntent() : getTargetIntent(); + if (result == null) { + Log.e(TAG, "ChooserTargetInfo#getFillInIntent: no fillIn intent available"); + } else if (mFillInIntent != null) { + result = new Intent(result); + result.fillIn(mFillInIntent, mFillInFlags); + } + return result; } @Override public boolean start(Activity activity, Bundle options) { - return mChooserTarget.sendIntent(activity, getFillInIntent()); + final Intent intent = getFillInIntent(); + if (intent == null) { + return false; + } + return mChooserTarget.sendIntent(activity, intent); } @Override public boolean startAsCaller(Activity activity, Bundle options, int userId) { - return mChooserTarget.sendIntentAsCaller(activity, getFillInIntent(), userId); + final Intent intent = getFillInIntent(); + if (intent == null) { + return false; + } + return mChooserTarget.sendIntentAsCaller(activity, intent, userId); } @Override public boolean startAsUser(Activity activity, Bundle options, UserHandle user) { - return mChooserTarget.sendIntentAsUser(activity, getFillInIntent(), user); + final Intent intent = getFillInIntent(); + if (intent == null) { + return false; + } + return mChooserTarget.sendIntentAsUser(activity, intent, user); } @Override @@ -395,6 +538,21 @@ public class ChooserActivity extends ResolverActivity { public Drawable getDisplayIcon() { return mDisplayIcon; } + + @Override + public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) { + return new ChooserTargetInfo(this, fillInIntent, flags); + } + + @Override + public List<Intent> getAllSourceIntents() { + final List<Intent> results = new ArrayList<>(); + if (mSourceInfo != null) { + // We only queried the service for the first one in our sourceinfo. + results.add(mSourceInfo.getAllSourceIntents().get(0)); + } + return results; + } } public class ChooserListAdapter extends ResolveListAdapter { @@ -542,4 +700,53 @@ public class ChooserActivity extends ResolverActivity { connection = c; } } + + static class RefinementResultReceiver extends ResultReceiver { + private ChooserActivity mChooserActivity; + private TargetInfo mSelectedTarget; + + public RefinementResultReceiver(ChooserActivity host, TargetInfo target, + Handler handler) { + super(handler); + mChooserActivity = host; + mSelectedTarget = target; + } + + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + if (mChooserActivity == null) { + Log.e(TAG, "Destroyed RefinementResultReceiver received a result"); + return; + } + if (resultData == null) { + Log.e(TAG, "RefinementResultReceiver received null resultData"); + return; + } + + switch (resultCode) { + case RESULT_CANCELED: + mChooserActivity.onRefinementCanceled(); + break; + case RESULT_OK: + Parcelable intentParcelable = resultData.getParcelable(Intent.EXTRA_INTENT); + if (intentParcelable instanceof Intent) { + mChooserActivity.onRefinementResult(mSelectedTarget, + (Intent) intentParcelable); + } else { + Log.e(TAG, "RefinementResultReceiver received RESULT_OK but no Intent" + + " in resultData with key Intent.EXTRA_INTENT"); + } + break; + default: + Log.w(TAG, "Unknown result code " + resultCode + + " sent to RefinementResultReceiver"); + break; + } + } + + public void destroy() { + mChooserActivity = null; + mSelectedTarget = null; + } + } } diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 4c6db24..644adb6 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -97,6 +97,12 @@ interface IVoiceInteractionManagerService { void showSessionForActiveService(IVoiceInteractionSessionShowCallback showCallback); /** + * Notifies the active service that a launch was requested from the Keyguard. This will only + * be called if {@link #activeServiceSupportsLaunchFromKeyguard()} returns true. + */ + void launchVoiceAssistFromKeyguard(); + + /** * Indicates whether there is a voice session running (but not necessarily showing). */ boolean isSessionRunning(); @@ -106,4 +112,10 @@ interface IVoiceInteractionManagerService { * assist gesture. */ boolean activeServiceSupportsAssist(); + + /** + * Indicates whether the currently active voice interaction service is capable of being launched + * from the lockscreen. + */ + boolean activeServiceSupportsLaunchFromKeyguard(); } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 8dd7836..2048664 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -102,7 +102,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic private int mLastSelected = AbsListView.INVALID_POSITION; private boolean mResolvingHome = false; private int mProfileSwitchMessageId = -1; - private Intent mIntent; + private final ArrayList<Intent> mIntents = new ArrayList<>(); private UsageStatsManager mUsm; private Map<String, UsageStats> mStats; @@ -229,7 +229,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); mIconDpi = am.getLauncherLargeIconDensity(); - mIntent = new Intent(intent); + mIntents.add(0, new Intent(intent)); mAdapter = createAdapter(this, initialIntents, rList, mLaunchedFromUid, alwaysUseOption); final int layoutId; @@ -250,7 +250,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return; } - int count = mAdapter.mList.size(); + int count = mAdapter.mDisplayList.size(); if (count > 1 || (count == 1 && mAdapter.getOtherProfile() != null)) { setContentView(layoutId); mAdapterView = (AbsListView) findViewById(R.id.resolver_list); @@ -376,8 +376,16 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } } + protected final void setAdditionalTargets(Intent[] intents) { + if (intents != null) { + for (Intent intent : intents) { + mIntents.add(intent); + } + } + } + public Intent getTargetIntent() { - return mIntent; + return mIntents.isEmpty() ? null : mIntents.get(0); } private String getReferrerPackageName() { @@ -630,8 +638,9 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } TargetInfo target = mAdapter.targetInfoForPosition(which, filtered); - onTargetSelected(target, always); - finish(); + if (onTargetSelected(target, always)) { + finish(); + } } /** @@ -641,7 +650,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return defIntent; } - protected void onTargetSelected(TargetInfo target, boolean alwaysCheck) { + protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) { final ResolveInfo ri = target.getResolveInfo(); final Intent intent = target != null ? target.getResolvedIntent() : null; @@ -728,7 +737,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic ComponentName[] set = new ComponentName[N]; int bestMatch = 0; for (int i=0; i<N; i++) { - ResolveInfo r = mAdapter.mOrigResolveList.get(i); + ResolveInfo r = mAdapter.mOrigResolveList.get(i).getResolveInfoAt(0); set[i] = new ComponentName(r.activityInfo.packageName, r.activityInfo.name); if (r.match > bestMatch) bestMatch = r.match; @@ -774,6 +783,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if (target != null) { safelyStartActivity(target); } + return true; } void safelyStartActivity(TargetInfo cti) { @@ -837,15 +847,17 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic private Drawable mDisplayIcon; private final CharSequence mExtendedInfo; private final Intent mResolvedIntent; + private final List<Intent> mSourceIntents = new ArrayList<>(); - DisplayResolveInfo(ResolveInfo pri, CharSequence pLabel, + DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel, CharSequence pInfo, Intent pOrigIntent) { + mSourceIntents.add(originalIntent); mResolveInfo = pri; mDisplayLabel = pLabel; mExtendedInfo = pInfo; final Intent intent = new Intent(pOrigIntent != null ? pOrigIntent : - getReplacementIntent(pri.activityInfo, mIntent)); + getReplacementIntent(pri.activityInfo, getTargetIntent())); intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT | Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP); final ActivityInfo ai = mResolveInfo.activityInfo; @@ -854,6 +866,16 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic mResolvedIntent = intent; } + private DisplayResolveInfo(DisplayResolveInfo other, Intent fillInIntent, int flags) { + mSourceIntents.addAll(other.getAllSourceIntents()); + mResolveInfo = other.mResolveInfo; + mDisplayLabel = other.mDisplayLabel; + mDisplayIcon = other.mDisplayIcon; + mExtendedInfo = other.mExtendedInfo; + mResolvedIntent = new Intent(other.mResolvedIntent); + mResolvedIntent.fillIn(fillInIntent, flags); + } + public ResolveInfo getResolveInfo() { return mResolveInfo; } @@ -866,6 +888,20 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return mDisplayIcon; } + @Override + public TargetInfo cloneFilledIn(Intent fillInIntent, int flags) { + return new DisplayResolveInfo(this, fillInIntent, flags); + } + + @Override + public List<Intent> getAllSourceIntents() { + return mSourceIntents; + } + + public void addAlternateSourceIntent(Intent alt) { + mSourceIntents.add(alt); + } + public void setDisplayIcon(Drawable icon) { mDisplayIcon = icon; } @@ -986,6 +1022,16 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic * @return The drawable that should be used to represent this target */ public Drawable getDisplayIcon(); + + /** + * Clone this target with the given fill-in information. + */ + public TargetInfo cloneFilledIn(Intent fillInIntent, int flags); + + /** + * @return the list of supported source intents deduped against this single target + */ + public List<Intent> getAllSourceIntents(); } class ResolveListAdapter extends BaseAdapter { @@ -998,8 +1044,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic protected final LayoutInflater mInflater; - List<DisplayResolveInfo> mList; - List<ResolveInfo> mOrigResolveList; + List<DisplayResolveInfo> mDisplayList; + List<ResolvedComponentInfo> mOrigResolveList; private int mLastChosenPosition = -1; private boolean mFilterLastUsed; @@ -1010,7 +1056,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic mBaseResolveList = rList; mLaunchedFromUid = launchedFromUid; mInflater = LayoutInflater.from(context); - mList = new ArrayList<>(); + mDisplayList = new ArrayList<>(); mFilterLastUsed = filterLastUsed; rebuildList(); } @@ -1027,7 +1073,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic public DisplayResolveInfo getFilteredItem() { if (mFilterLastUsed && mLastChosenPosition >= 0) { // Not using getItem since it offsets to dodge this position for the list - return mList.get(mLastChosenPosition); + return mDisplayList.get(mLastChosenPosition); } return null; } @@ -1048,11 +1094,12 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } private void rebuildList() { - List<ResolveInfo> currentResolveList; + List<ResolvedComponentInfo> currentResolveList = null; try { + final Intent primaryIntent = getTargetIntent(); mLastChosen = AppGlobals.getPackageManager().getLastChosenActivity( - mIntent, mIntent.resolveTypeIfNeeded(getContentResolver()), + primaryIntent, primaryIntent.resolveTypeIfNeeded(getContentResolver()), PackageManager.MATCH_DEFAULT_ONLY); } catch (RemoteException re) { Log.d(TAG, "Error calling setLastChosenActivity\n" + re); @@ -1060,15 +1107,27 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic // Clear the value of mOtherProfile from previous call. mOtherProfile = null; - mList.clear(); + mDisplayList.clear(); if (mBaseResolveList != null) { - currentResolveList = mOrigResolveList = mBaseResolveList; + currentResolveList = mOrigResolveList = new ArrayList<>(); + addResolveListDedupe(currentResolveList, getTargetIntent(), mBaseResolveList); } else { - currentResolveList = mOrigResolveList = mPm.queryIntentActivities(mIntent, - PackageManager.MATCH_DEFAULT_ONLY - | (shouldGetResolvedFilter() ? PackageManager.GET_RESOLVED_FILTER : 0) - | (shouldGetActivityMetadata() ? PackageManager.GET_META_DATA : 0) - ); + final boolean shouldGetResolvedFilter = shouldGetResolvedFilter(); + final boolean shouldGetActivityMetadata = shouldGetActivityMetadata(); + for (int i = 0, N = mIntents.size(); i < N; i++) { + final Intent intent = mIntents.get(i); + final List<ResolveInfo> infos = mPm.queryIntentActivities(intent, + PackageManager.MATCH_DEFAULT_ONLY + | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) + | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0)); + if (infos != null) { + if (currentResolveList == null) { + currentResolveList = mOrigResolveList = new ArrayList<>(); + } + addResolveListDedupe(currentResolveList, intent, infos); + } + } + // Filter out any activities that the launched uid does not // have permission for. We don't do this when we have an explicit // list of resolved activities, because that only happens when @@ -1076,14 +1135,15 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic // they gave us. if (currentResolveList != null) { for (int i=currentResolveList.size()-1; i >= 0; i--) { - ActivityInfo ai = currentResolveList.get(i).activityInfo; + ActivityInfo ai = currentResolveList.get(i) + .getResolveInfoAt(0).activityInfo; int granted = ActivityManager.checkComponentPermission( ai.permission, mLaunchedFromUid, ai.applicationInfo.uid, ai.exported); if (granted != PackageManager.PERMISSION_GRANTED) { // Access not allowed! if (mOrigResolveList == currentResolveList) { - mOrigResolveList = new ArrayList<ResolveInfo>(mOrigResolveList); + mOrigResolveList = new ArrayList<>(mOrigResolveList); } currentResolveList.remove(i); } @@ -1094,9 +1154,10 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if ((currentResolveList != null) && ((N = currentResolveList.size()) > 0)) { // Only display the first matches that are either of equal // priority or have asked to be default options. - ResolveInfo r0 = currentResolveList.get(0); + ResolvedComponentInfo rci0 = currentResolveList.get(0); + ResolveInfo r0 = rci0.getResolveInfoAt(0); for (int i=1; i<N; i++) { - ResolveInfo ri = currentResolveList.get(i); + ResolveInfo ri = currentResolveList.get(i).getResolveInfoAt(0); if (DEBUG) Log.v( TAG, r0.activityInfo.name + "=" + @@ -1107,7 +1168,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic r0.isDefault != ri.isDefault) { while (i < N) { if (mOrigResolveList == currentResolveList) { - mOrigResolveList = new ArrayList<ResolveInfo>(mOrigResolveList); + mOrigResolveList = new ArrayList<>(mOrigResolveList); } currentResolveList.remove(i); N--; @@ -1115,9 +1176,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } } if (N > 1) { - Comparator<ResolveInfo> rComparator = - new ResolverComparator(ResolverActivity.this, mIntent); - Collections.sort(currentResolveList, rComparator); + Collections.sort(currentResolveList, + new ResolverComparator(ResolverActivity.this, getTargetIntent())); } // First put the initial items at the top. if (mInitialIntents != null) { @@ -1146,14 +1206,15 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic ri.nonLocalizedLabel = li.getNonLocalizedLabel(); ri.icon = li.getIconResource(); } - addResolveInfo(new DisplayResolveInfo(ri, + addResolveInfo(new DisplayResolveInfo(ii, ri, ri.loadLabel(getPackageManager()), null, ii)); } } // Check for applications with same name and use application name or // package name if necessary - r0 = currentResolveList.get(0); + rci0 = currentResolveList.get(0); + r0 = rci0.getResolveInfoAt(0); int start = 0; CharSequence r0Label = r0.loadLabel(mPm); mHasExtendedInfo = false; @@ -1161,7 +1222,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if (r0Label == null) { r0Label = r0.activityInfo.packageName; } - ResolveInfo ri = currentResolveList.get(i); + ResolvedComponentInfo rci = currentResolveList.get(i); + ResolveInfo ri = rci.getResolveInfoAt(0); CharSequence riLabel = ri.loadLabel(mPm); if (riLabel == null) { riLabel = ri.activityInfo.packageName; @@ -1169,13 +1231,14 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if (riLabel.equals(r0Label)) { continue; } - processGroup(currentResolveList, start, (i-1), r0, r0Label); + processGroup(currentResolveList, start, (i-1), rci0, r0Label); + rci0 = rci; r0 = ri; r0Label = riLabel; start = i; } // Process last group - processGroup(currentResolveList, start, (N-1), r0, r0Label); + processGroup(currentResolveList, start, (N-1), rci0, r0Label); } // Layout doesn't handle both profile button and last chosen @@ -1188,6 +1251,36 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic onListRebuilt(); } + private void addResolveListDedupe(List<ResolvedComponentInfo> into, Intent intent, + List<ResolveInfo> from) { + final int fromCount = from.size(); + final int intoCount = into.size(); + for (int i = 0; i < fromCount; i++) { + final ResolveInfo newInfo = from.get(i); + boolean found = false; + // Only loop to the end of into as it was before we started; no dupes in from. + for (int j = 0; j < intoCount; j++) { + final ResolvedComponentInfo rci = into.get(i); + if (isSameResolvedComponent(newInfo, rci)) { + found = true; + rci.add(intent, newInfo); + break; + } + } + if (!found) { + into.add(new ResolvedComponentInfo(new ComponentName( + newInfo.activityInfo.packageName, newInfo.activityInfo.name), + intent, newInfo)); + } + } + } + + private boolean isSameResolvedComponent(ResolveInfo a, ResolvedComponentInfo b) { + final ActivityInfo ai = a.activityInfo; + return ai.packageName.equals(b.name.getPackageName()) + && ai.name.equals(b.name.getClassName()); + } + public void onListRebuilt() { // This space for rent } @@ -1196,18 +1289,18 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic return mFilterLastUsed; } - private void processGroup(List<ResolveInfo> rList, int start, int end, ResolveInfo ro, - CharSequence roLabel) { + private void processGroup(List<ResolvedComponentInfo> rList, int start, int end, + ResolvedComponentInfo ro, CharSequence roLabel) { // Process labels from start to i int num = end - start+1; if (num == 1) { // No duplicate labels. Use label for entry at start - addResolveInfo(new DisplayResolveInfo(ro, roLabel, null, null)); - updateLastChosenPosition(ro); + addResolveInfoWithAlternates(ro, null, roLabel); } else { mHasExtendedInfo = true; boolean usePkg = false; - CharSequence startApp = ro.activityInfo.applicationInfo.loadLabel(mPm); + CharSequence startApp = ro.getResolveInfoAt(0).activityInfo.applicationInfo + .loadLabel(mPm); if (startApp == null) { usePkg = true; } @@ -1217,7 +1310,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic new HashSet<CharSequence>(); duplicates.add(startApp); for (int j = start+1; j <= end ; j++) { - ResolveInfo jRi = rList.get(j); + ResolveInfo jRi = rList.get(j).getResolveInfoAt(0); CharSequence jApp = jRi.activityInfo.applicationInfo.loadLabel(mPm); if ( (jApp == null) || (duplicates.contains(jApp))) { usePkg = true; @@ -1230,26 +1323,46 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic duplicates.clear(); } for (int k = start; k <= end; k++) { - ResolveInfo add = rList.get(k); + final ResolvedComponentInfo rci = rList.get(k); + final ResolveInfo add = rci.getResolveInfoAt(0); + final CharSequence extraInfo; if (usePkg) { - // Use application name for all entries from start to end-1 - addResolveInfo(new DisplayResolveInfo(add, roLabel, - add.activityInfo.packageName, null)); - } else { // Use package name for all entries from start to end-1 - addResolveInfo(new DisplayResolveInfo(add, roLabel, - add.activityInfo.applicationInfo.loadLabel(mPm), null)); + extraInfo = add.activityInfo.packageName; + } else { + // Use application name for all entries from start to end-1 + extraInfo = add.activityInfo.applicationInfo.loadLabel(mPm); } - updateLastChosenPosition(add); + addResolveInfoWithAlternates(rci, extraInfo, roLabel); + } + } + } + + private void addResolveInfoWithAlternates(ResolvedComponentInfo rci, + CharSequence extraInfo, CharSequence roLabel) { + final int count = rci.getCount(); + final Intent intent = rci.getIntentAt(0); + final ResolveInfo add = rci.getResolveInfoAt(0); + final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent); + final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, roLabel, + extraInfo, replaceIntent); + addResolveInfo(dri); + if (replaceIntent == intent) { + // Only add alternates if we didn't get a specific replacement from + // the caller. If we have one it trumps potential alternates. + for (int i = 1, N = count; i < N; i++) { + final Intent altIntent = rci.getIntentAt(i); + dri.addAlternateSourceIntent(altIntent); } } + updateLastChosenPosition(add); } private void updateLastChosenPosition(ResolveInfo info) { if (mLastChosen != null && mLastChosen.activityInfo.packageName.equals(info.activityInfo.packageName) && mLastChosen.activityInfo.name.equals(info.activityInfo.name)) { - mLastChosenPosition = mList.size() - 1; + mLastChosenPosition = mDisplayList.size() - 1; } } @@ -1259,20 +1372,21 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic // The first one we see gets special treatment. mOtherProfile = dri; } else { - mList.add(dri); + mDisplayList.add(dri); } } public ResolveInfo resolveInfoForPosition(int position, boolean filtered) { - return (filtered ? getItem(position) : mList.get(position)).getResolveInfo(); + return (filtered ? getItem(position) : mDisplayList.get(position)) + .getResolveInfo(); } public TargetInfo targetInfoForPosition(int position, boolean filtered) { - return filtered ? getItem(position) : mList.get(position); + return filtered ? getItem(position) : mDisplayList.get(position); } public int getCount() { - int result = mList.size(); + int result = mDisplayList.size(); if (mFilterLastUsed && mLastChosenPosition >= 0) { result--; } @@ -1283,7 +1397,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if (mFilterLastUsed && mLastChosenPosition >= 0 && position >= mLastChosenPosition) { position++; } - return mList.get(position); + return mDisplayList.get(position); } public long getItemId(int position) { @@ -1295,8 +1409,8 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } public boolean hasResolvedTarget(ResolveInfo info) { - for (int i = 0, N = mList.size(); i < N; i++) { - if (info.equals(mList.get(i).getResolveInfo())) { + for (int i = 0, N = mDisplayList.size(); i < N; i++) { + if (info.equals(mDisplayList.get(i).getResolveInfo())) { return true; } } @@ -1304,11 +1418,12 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } protected int getDisplayResolveInfoCount() { - return mList.size(); + return mDisplayList.size(); } protected DisplayResolveInfo getDisplayResolveInfo(int index) { - return mList.get(index); + // Used to query services. We only query services for primary targets, not alternates. + return mDisplayList.get(index); } public final View getView(int position, View convertView, ViewGroup parent) { @@ -1349,6 +1464,52 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } } + static final class ResolvedComponentInfo { + public final ComponentName name; + private final List<Intent> mIntents = new ArrayList<>(); + private final List<ResolveInfo> mResolveInfos = new ArrayList<>(); + + public ResolvedComponentInfo(ComponentName name, Intent intent, ResolveInfo info) { + this.name = name; + add(intent, info); + } + + public void add(Intent intent, ResolveInfo info) { + mIntents.add(intent); + mResolveInfos.add(info); + } + + public int getCount() { + return mIntents.size(); + } + + public Intent getIntentAt(int index) { + return index >= 0 ? mIntents.get(index) : null; + } + + public ResolveInfo getResolveInfoAt(int index) { + return index >= 0 ? mResolveInfos.get(index) : null; + } + + public int findIntent(Intent intent) { + for (int i = 0, N = mIntents.size(); i < N; i++) { + if (intent.equals(mIntents.get(i))) { + return i; + } + } + return -1; + } + + public int findResolveInfo(ResolveInfo info) { + for (int i = 0, N = mResolveInfos.size(); i < N; i++) { + if (info.equals(mResolveInfos.get(i))) { + return i; + } + } + return -1; + } + } + static class ViewHolder { public TextView text; public TextView text2; @@ -1435,7 +1596,7 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic && match <= IntentFilter.MATCH_CATEGORY_PATH; } - class ResolverComparator implements Comparator<ResolveInfo> { + class ResolverComparator implements Comparator<ResolvedComponentInfo> { private final Collator mCollator; private final boolean mHttp; @@ -1446,7 +1607,10 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic } @Override - public int compare(ResolveInfo lhs, ResolveInfo rhs) { + public int compare(ResolvedComponentInfo lhsp, ResolvedComponentInfo rhsp) { + final ResolveInfo lhs = lhsp.getResolveInfoAt(0); + final ResolveInfo rhs = rhsp.getResolveInfoAt(0); + // We want to put the one targeted to another user at the end of the dialog. if (lhs.targetUserId != UserHandle.USER_CURRENT) { return 1; @@ -1487,7 +1651,6 @@ public class ResolverActivity extends Activity implements AdapterView.OnItemClic if (stats != null) { return stats.getTotalTimeInForeground(); } - } return 0; } diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java index 52485dd..ce94727 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeSwitchingController.java @@ -196,7 +196,7 @@ public class InputMethodSubtypeSwitchingController { } public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList( - boolean showSubtypes, boolean inputShown, boolean isScreenLocked) { + boolean showSubtypes, boolean includeAuxiliarySubtypes, boolean isScreenLocked) { final ArrayList<ImeSubtypeListItem> imList = new ArrayList<ImeSubtypeListItem>(); final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis = @@ -205,6 +205,12 @@ public class InputMethodSubtypeSwitchingController { if (immis == null || immis.size() == 0) { return Collections.emptyList(); } + if (isScreenLocked && includeAuxiliarySubtypes) { + if (DEBUG) { + Slog.w(TAG, "Auxiliary subtypes are not allowed to be shown in lock screen."); + } + includeAuxiliarySubtypes = false; + } mSortedImmis.clear(); mSortedImmis.putAll(immis); for (InputMethodInfo imi : mSortedImmis.keySet()) { @@ -227,7 +233,7 @@ public class InputMethodSubtypeSwitchingController { final String subtypeHashCode = String.valueOf(subtype.hashCode()); // We show all enabled IMEs and subtypes when an IME is shown. if (enabledSubtypeSet.contains(subtypeHashCode) - && ((inputShown && !isScreenLocked) || !subtype.isAuxiliary())) { + && (includeAuxiliarySubtypes || !subtype.isAuxiliary())) { final CharSequence subtypeLabel = subtype.overridesImplicitlyEnabledSubtype() ? null : subtype .getDisplayName(mContext, imi.getPackageName(), @@ -516,8 +522,8 @@ public class InputMethodSubtypeSwitchingController { } public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListLocked(boolean showSubtypes, - boolean inputShown, boolean isScreenLocked) { + boolean includingAuxiliarySubtypes, boolean isScreenLocked) { return mSubtypeList.getSortedInputMethodAndSubtypeList( - showSubtypes, inputShown, isScreenLocked); + showSubtypes, includingAuxiliarySubtypes, isScreenLocked); } } diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java index 6173832..9277f9b 100644 --- a/core/java/com/android/internal/logging/MetricsLogger.java +++ b/core/java/com/android/internal/logging/MetricsLogger.java @@ -18,6 +18,7 @@ package com.android.internal.logging; import android.content.Context; import android.os.Build; +import android.view.View; /** * Log all the things. @@ -33,6 +34,10 @@ public class MetricsLogger implements MetricsConstants { public static final int ACTION_BAN_APP_NOTES = 146; public static final int NOTIFICATION_ZEN_MODE_EVENT_RULE = 147; public static final int ACTION_DISMISS_ALL_NOTES = 148; + public static final int QS_DND_DETAILS = 149; + public static final int QS_BLUETOOTH_DETAILS = 150; + public static final int QS_CAST_DETAILS = 151; + public static final int QS_WIFI_DETAILS = 152; public static void visible(Context context, int category) throws IllegalArgumentException { if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) { @@ -41,13 +46,27 @@ public class MetricsLogger implements MetricsConstants { EventLogTags.writeSysuiViewVisibility(category, 100); } - public static void hidden(Context context, int category) { + public static void hidden(Context context, int category) throws IllegalArgumentException { if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) { throw new IllegalArgumentException("Must define metric category"); } EventLogTags.writeSysuiViewVisibility(category, 0); } + public static void visibility(Context context, int category, boolean visibile) + throws IllegalArgumentException { + if (visibile) { + visible(context, category); + } else { + hidden(context, category); + } + } + + public static void visibility(Context context, int category, int vis) + throws IllegalArgumentException { + visibility(context, category, vis == View.VISIBLE); + } + public static void action(Context context, int category) { action(context, category, ""); } diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java index 240d9df..d77b998 100644 --- a/core/java/com/android/internal/os/BinderInternal.java +++ b/core/java/com/android/internal/os/BinderInternal.java @@ -20,6 +20,8 @@ import android.os.IBinder; import android.os.SystemClock; import android.util.EventLog; +import dalvik.system.VMRuntime; + import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -96,7 +98,7 @@ public class BinderInternal { public static void forceGc(String reason) { EventLog.writeEvent(2741, reason); - Runtime.getRuntime().gc(); + VMRuntime.getRuntime().requestConcurrentGC(); } static void forceBinderGc() { diff --git a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java index 9711c3b..8586d76 100644 --- a/core/java/com/android/internal/os/MobileRadioPowerCalculator.java +++ b/core/java/com/android/internal/os/MobileRadioPowerCalculator.java @@ -52,7 +52,7 @@ public class MobileRadioPowerCalculator extends PowerCalculator { public MobileRadioPowerCalculator(PowerProfile profile, BatteryStats stats) { mPowerRadioOn = profile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE); for (int i = 0; i < mPowerBins.length; i++) { - mPowerBins[i] = profile.getAveragePower(PowerProfile.POWER_RADIO_ACTIVE, i); + mPowerBins[i] = profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i); } mPowerScan = profile.getAveragePower(PowerProfile.POWER_RADIO_SCANNING); mStats = stats; @@ -128,7 +128,9 @@ public class MobileRadioPowerCalculator extends PowerCalculator { } if (power != 0) { - app.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs; + if (signalTimeMs != 0) { + app.noCoveragePercent = noCoverageTimeMs * 100.0 / signalTimeMs; + } app.mobileActive = remainingActiveTimeMs; app.mobileActiveCount = stats.getMobileRadioActiveUnknownCount(statsType); app.mobileRadioPowerMah = power; diff --git a/core/java/android/view/PhoneFallbackEventHandler.java b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java index 350650d..2cb9c25 100644 --- a/core/java/android/view/PhoneFallbackEventHandler.java +++ b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.view; +package com.android.internal.policy; import android.app.KeyguardManager; import android.app.SearchManager; @@ -28,10 +28,11 @@ import android.os.UserHandle; import android.provider.Settings; import android.telephony.TelephonyManager; import android.util.Log; -import android.view.View; -import android.view.HapticFeedbackConstants; import android.view.FallbackEventHandler; +import android.view.HapticFeedbackConstants; import android.view.KeyEvent; +import android.view.View; +import com.android.internal.policy.PhoneWindow; /** * @hide diff --git a/core/java/android/view/PhoneLayoutInflater.java b/core/java/com/android/internal/policy/PhoneLayoutInflater.java index 7d89a0b..991b6bb 100644 --- a/core/java/android/view/PhoneLayoutInflater.java +++ b/core/java/com/android/internal/policy/PhoneLayoutInflater.java @@ -14,10 +14,12 @@ * limitations under the License. */ -package android.view; +package com.android.internal.policy; import android.content.Context; import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; /** * @hide diff --git a/core/java/android/view/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java index a3e7a10..a578a6e 100644 --- a/core/java/android/view/PhoneWindow.java +++ b/core/java/com/android/internal/policy/PhoneWindow.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package android.view; +package com.android.internal.policy; import static android.view.View.MeasureSpec.AT_MOST; import static android.view.View.MeasureSpec.EXACTLY; @@ -27,6 +27,34 @@ import android.app.ActivityManagerNative; import android.app.SearchManager; import android.os.UserHandle; +import android.view.ActionMode; +import android.view.ContextThemeWrapper; +import android.view.Display; +import android.view.Gravity; +import android.view.IRotationWatcher.Stub; +import android.view.IWindowManager; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.InputQueue; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.SearchEvent; +import android.view.SurfaceHolder.Callback2; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.ViewManager; +import android.view.ViewParent; +import android.view.ViewRootImpl; +import android.view.ViewStub; +import android.view.ViewTreeObserver.OnPreDrawListener; +import android.view.Window; +import android.view.WindowInsets; +import android.view.WindowManager; import com.android.internal.R; import com.android.internal.util.ScreenShapeHelper; import com.android.internal.view.FloatingActionMode; @@ -67,7 +95,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemProperties; import android.transition.Scene; import android.transition.Transition; import android.transition.TransitionInflater; @@ -140,7 +167,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private ViewGroup mContentRoot; - SurfaceHolder.Callback2 mTakeSurfaceCallback; + Callback2 mTakeSurfaceCallback; InputQueue.Callback mTakeInputQueueCallback; @@ -427,7 +454,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } @Override - public void takeSurface(SurfaceHolder.Callback2 callback) { + public void takeSurface(Callback2 callback) { mTakeSurfaceCallback = callback; } @@ -2181,7 +2208,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private ActionBarContextView mPrimaryActionModeView; private PopupWindow mPrimaryActionModePopup; private Runnable mShowPrimaryActionModePopup; - private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener; + private OnPreDrawListener mFloatingToolbarPreDrawListener; private View mFloatingActionModeOriginatingView; private FloatingToolbar mFloatingToolbar; @@ -3354,7 +3381,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { mContext, callback, originatingView, mFloatingToolbar); mFloatingActionModeOriginatingView = originatingView; mFloatingToolbarPreDrawListener = - new ViewTreeObserver.OnPreDrawListener() { + new OnPreDrawListener() { @Override public boolean onPreDraw() { mode.updateViewLocationInWindow(); @@ -4718,7 +4745,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { } - static class RotationWatcher extends IRotationWatcher.Stub { + static class RotationWatcher extends Stub { private Handler mHandler; private final Runnable mRotationChanged = new Runnable() { public void run() { diff --git a/core/java/com/android/internal/transition/EpicenterClipReveal.java b/core/java/com/android/internal/transition/EpicenterClipReveal.java deleted file mode 100644 index 1a6736a..0000000 --- a/core/java/com/android/internal/transition/EpicenterClipReveal.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.internal.transition; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.RectEvaluator; -import android.animation.TimeInterpolator; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Rect; -import android.transition.TransitionValues; -import android.transition.Visibility; -import android.util.AttributeSet; -import android.util.Property; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; -import android.view.animation.PathInterpolator; - -import com.android.internal.R; - -/** - * EpicenterClipReveal captures the {@link View#getClipBounds()} before and - * after the scene change and animates between those and the epicenter bounds - * during a visibility transition. - */ -public class EpicenterClipReveal extends Visibility { - private static final String PROPNAME_CLIP = "android:epicenterReveal:clip"; - private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds"; - - private final TimeInterpolator mInterpolatorX; - private final TimeInterpolator mInterpolatorY; - private final boolean mCenterClipBounds; - - public EpicenterClipReveal() { - mInterpolatorX = null; - mInterpolatorY = null; - mCenterClipBounds = false; - } - - public EpicenterClipReveal(Context context, AttributeSet attrs) { - super(context, attrs); - - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.EpicenterClipReveal, 0, 0); - - mCenterClipBounds = a.getBoolean(R.styleable.EpicenterClipReveal_centerClipBounds, false); - - final int interpolatorX = a.getResourceId(R.styleable.EpicenterClipReveal_interpolatorX, 0); - if (interpolatorX != 0) { - mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX); - } else { - mInterpolatorX = TransitionConstants.LINEAR_OUT_SLOW_IN; - } - - final int interpolatorY = a.getResourceId(R.styleable.EpicenterClipReveal_interpolatorY, 0); - if (interpolatorY != 0) { - mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY); - } else { - mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN; - } - - a.recycle(); - } - - @Override - public void captureStartValues(TransitionValues transitionValues) { - super.captureStartValues(transitionValues); - captureValues(transitionValues); - } - - @Override - public void captureEndValues(TransitionValues transitionValues) { - super.captureEndValues(transitionValues); - captureValues(transitionValues); - } - - private void captureValues(TransitionValues values) { - final View view = values.view; - if (view.getVisibility() == View.GONE) { - return; - } - - final Rect clip = view.getClipBounds(); - values.values.put(PROPNAME_CLIP, clip); - - if (clip == null) { - final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); - values.values.put(PROPNAME_BOUNDS, bounds); - } - } - - @Override - public Animator onAppear(ViewGroup sceneRoot, View view, - TransitionValues startValues, TransitionValues endValues) { - if (endValues == null) { - return null; - } - - final Rect end = getBestRect(endValues); - final Rect start = getEpicenterOrCenter(end); - - // Prepare the view. - view.setClipBounds(start); - - return createRectAnimator(view, start, end, endValues, mInterpolatorX, mInterpolatorY); - } - - @Override - public Animator onDisappear(ViewGroup sceneRoot, View view, - TransitionValues startValues, TransitionValues endValues) { - if (startValues == null) { - return null; - } - - final Rect start = getBestRect(startValues); - final Rect end = getEpicenterOrCenter(start); - - // Prepare the view. - view.setClipBounds(start); - - return createRectAnimator(view, start, end, endValues, mInterpolatorX, mInterpolatorY); - } - - private Rect getEpicenterOrCenter(Rect bestRect) { - final Rect epicenter = getEpicenter(); - if (epicenter != null) { - // Translate the clip bounds to be centered within the target bounds. - if (mCenterClipBounds) { - final int offsetX = bestRect.centerX() - epicenter.centerX(); - final int offsetY = bestRect.centerY() - epicenter.centerY(); - epicenter.offset(offsetX, offsetY); - } - return epicenter; - } - - final int centerX = bestRect.centerX(); - final int centerY = bestRect.centerY(); - return new Rect(centerX, centerY, centerX, centerY); - } - - private Rect getBestRect(TransitionValues values) { - final Rect clipRect = (Rect) values.values.get(PROPNAME_CLIP); - if (clipRect == null) { - return (Rect) values.values.get(PROPNAME_BOUNDS); - } - return clipRect; - } - - private static Animator createRectAnimator(final View view, Rect start, Rect end, - TransitionValues endValues, TimeInterpolator interpolatorX, - TimeInterpolator interpolatorY) { - final RectEvaluator evaluator = new RectEvaluator(new Rect()); - final Rect terminalClip = (Rect) endValues.values.get(PROPNAME_CLIP); - - final ClipDimenProperty propX = new ClipDimenProperty(ClipDimenProperty.TARGET_X); - final ObjectAnimator animX = ObjectAnimator.ofObject(view, propX, evaluator, start, end); - if (interpolatorX != null) { - animX.setInterpolator(interpolatorX); - } - - final ClipDimenProperty propY = new ClipDimenProperty(ClipDimenProperty.TARGET_Y); - final ObjectAnimator animY = ObjectAnimator.ofObject(view, propY, evaluator, start, end); - if (interpolatorY != null) { - animY.setInterpolator(interpolatorY); - } - - final AnimatorSet animSet = new AnimatorSet(); - animSet.playTogether(animX, animY); - animSet.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - view.setClipBounds(terminalClip); - } - }); - return animSet; - } - - private static class ClipDimenProperty extends Property<View, Rect> { - public static final char TARGET_X = 'x'; - public static final char TARGET_Y = 'y'; - - private final Rect mTempRect = new Rect(); - - private final int mTargetDimension; - - public ClipDimenProperty(char targetDimension) { - super(Rect.class, "clip_bounds_" + targetDimension); - - mTargetDimension = targetDimension; - } - - @Override - public Rect get(View object) { - final Rect tempRect = mTempRect; - if (!object.getClipBounds(tempRect)) { - tempRect.setEmpty(); - } - return tempRect; - } - - @Override - public void set(View object, Rect value) { - final Rect tempRect = mTempRect; - if (object.getClipBounds(tempRect)) { - if (mTargetDimension == TARGET_X) { - tempRect.left = value.left; - tempRect.right = value.right; - } else { - tempRect.top = value.top; - tempRect.bottom = value.bottom; - } - object.setClipBounds(tempRect); - } - } - } -} diff --git a/core/java/com/android/internal/transition/EpicenterTranslate.java b/core/java/com/android/internal/transition/EpicenterTranslate.java deleted file mode 100644 index eac3ff8..0000000 --- a/core/java/com/android/internal/transition/EpicenterTranslate.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.internal.transition; - -import com.android.internal.R; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.TimeInterpolator; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Rect; -import android.transition.TransitionValues; -import android.transition.Visibility; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; - -/** - * EpicenterTranslate captures the {@link View#getTranslationX()} and - * {@link View#getTranslationY()} before and after the scene change and - * animates between those and the epicenter's center during a visibility - * transition. - */ -public class EpicenterTranslate extends Visibility { - private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds"; - private static final String PROPNAME_TRANSLATE_X = "android:epicenterReveal:translateX"; - private static final String PROPNAME_TRANSLATE_Y = "android:epicenterReveal:translateY"; - private static final String PROPNAME_TRANSLATE_Z = "android:epicenterReveal:translateZ"; - private static final String PROPNAME_Z = "android:epicenterReveal:z"; - - private final TimeInterpolator mInterpolatorX; - private final TimeInterpolator mInterpolatorY; - private final TimeInterpolator mInterpolatorZ; - - public EpicenterTranslate() { - mInterpolatorX = null; - mInterpolatorY = null; - mInterpolatorZ = null; - } - - public EpicenterTranslate(Context context, AttributeSet attrs) { - super(context, attrs); - - final TypedArray a = context.obtainStyledAttributes(attrs, - R.styleable.EpicenterTranslate, 0, 0); - - final int interpolatorX = a.getResourceId(R.styleable.EpicenterTranslate_interpolatorX, 0); - if (interpolatorX != 0) { - mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX); - } else { - mInterpolatorX = TransitionConstants.FAST_OUT_SLOW_IN; - } - - final int interpolatorY = a.getResourceId(R.styleable.EpicenterTranslate_interpolatorY, 0); - if (interpolatorY != 0) { - mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY); - } else { - mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN; - } - - final int interpolatorZ = a.getResourceId(R.styleable.EpicenterTranslate_interpolatorZ, 0); - if (interpolatorZ != 0) { - mInterpolatorZ = AnimationUtils.loadInterpolator(context, interpolatorZ); - } else { - mInterpolatorZ = TransitionConstants.FAST_OUT_SLOW_IN; - } - - a.recycle(); - } - - @Override - public void captureStartValues(TransitionValues transitionValues) { - super.captureStartValues(transitionValues); - captureValues(transitionValues); - } - - @Override - public void captureEndValues(TransitionValues transitionValues) { - super.captureEndValues(transitionValues); - captureValues(transitionValues); - } - - private void captureValues(TransitionValues values) { - final View view = values.view; - if (view.getVisibility() == View.GONE) { - return; - } - - final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); - values.values.put(PROPNAME_BOUNDS, bounds); - values.values.put(PROPNAME_TRANSLATE_X, view.getTranslationX()); - values.values.put(PROPNAME_TRANSLATE_Y, view.getTranslationY()); - values.values.put(PROPNAME_TRANSLATE_Z, view.getTranslationZ()); - values.values.put(PROPNAME_Z, view.getZ()); - } - - @Override - public Animator onAppear(ViewGroup sceneRoot, View view, - TransitionValues startValues, TransitionValues endValues) { - if (endValues == null) { - return null; - } - - final Rect end = (Rect) endValues.values.get(PROPNAME_BOUNDS); - final Rect start = getEpicenterOrCenter(end); - final float startX = start.centerX() - end.centerX(); - final float startY = start.centerY() - end.centerY(); - final float startZ = 0 - (float) endValues.values.get(PROPNAME_Z); - - // Translate the view to be centered on the epicenter. - view.setTranslationX(startX); - view.setTranslationY(startY); - view.setTranslationZ(startZ); - - final float endX = (float) endValues.values.get(PROPNAME_TRANSLATE_X); - final float endY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y); - final float endZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z); - return createAnimator(view, startX, startY, startZ, endX, endY, endZ, - mInterpolatorX, mInterpolatorY, mInterpolatorZ); - } - - @Override - public Animator onDisappear(ViewGroup sceneRoot, View view, - TransitionValues startValues, TransitionValues endValues) { - if (startValues == null) { - return null; - } - - final Rect start = (Rect) endValues.values.get(PROPNAME_BOUNDS); - final Rect end = getEpicenterOrCenter(start); - final float endX = end.centerX() - start.centerX(); - final float endY = end.centerY() - start.centerY(); - final float endZ = 0 - (float) startValues.values.get(PROPNAME_Z); - - final float startX = (float) endValues.values.get(PROPNAME_TRANSLATE_X); - final float startY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y); - final float startZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z); - return createAnimator(view, startX, startY, startZ, endX, endY, endZ, - mInterpolatorX, mInterpolatorY, mInterpolatorZ); - } - - private Rect getEpicenterOrCenter(Rect bestRect) { - final Rect epicenter = getEpicenter(); - if (epicenter != null) { - return epicenter; - } - - final int centerX = bestRect.centerX(); - final int centerY = bestRect.centerY(); - return new Rect(centerX, centerY, centerX, centerY); - } - - private static Animator createAnimator(final View view, float startX, float startY, - float startZ, float endX, float endY, float endZ, TimeInterpolator interpolatorX, - TimeInterpolator interpolatorY, TimeInterpolator interpolatorZ) { - final ObjectAnimator animX = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, startX, endX); - if (interpolatorX != null) { - animX.setInterpolator(interpolatorX); - } - - final ObjectAnimator animY = ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, startY, endY); - if (interpolatorY != null) { - animY.setInterpolator(interpolatorY); - } - - final ObjectAnimator animZ = ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, startZ, endZ); - if (interpolatorZ != null) { - animZ.setInterpolator(interpolatorZ); - } - - final AnimatorSet animSet = new AnimatorSet(); - animSet.playTogether(animX, animY, animZ); - return animSet; - } -} diff --git a/core/java/com/android/internal/transition/EpicenterTranslateClipReveal.java b/core/java/com/android/internal/transition/EpicenterTranslateClipReveal.java new file mode 100644 index 0000000..2c10297 --- /dev/null +++ b/core/java/com/android/internal/transition/EpicenterTranslateClipReveal.java @@ -0,0 +1,328 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.transition; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.animation.TypeEvaluator; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Rect; +import android.transition.TransitionValues; +import android.transition.Visibility; +import android.util.AttributeSet; +import android.util.Property; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AnimationUtils; + +import com.android.internal.R; + +/** + * EpicenterTranslateClipReveal captures the clip bounds and translation values + * before and after the scene change and animates between those and the + * epicenter bounds during a visibility transition. + */ +public class EpicenterTranslateClipReveal extends Visibility { + private static final String PROPNAME_CLIP = "android:epicenterReveal:clip"; + private static final String PROPNAME_BOUNDS = "android:epicenterReveal:bounds"; + private static final String PROPNAME_TRANSLATE_X = "android:epicenterReveal:translateX"; + private static final String PROPNAME_TRANSLATE_Y = "android:epicenterReveal:translateY"; + private static final String PROPNAME_TRANSLATE_Z = "android:epicenterReveal:translateZ"; + private static final String PROPNAME_Z = "android:epicenterReveal:z"; + + private final TimeInterpolator mInterpolatorX; + private final TimeInterpolator mInterpolatorY; + private final TimeInterpolator mInterpolatorZ; + + public EpicenterTranslateClipReveal() { + mInterpolatorX = null; + mInterpolatorY = null; + mInterpolatorZ = null; + } + + public EpicenterTranslateClipReveal(Context context, AttributeSet attrs) { + super(context, attrs); + + final TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.EpicenterTranslateClipReveal, 0, 0); + + final int interpolatorX = a.getResourceId( + R.styleable.EpicenterTranslateClipReveal_interpolatorX, 0); + if (interpolatorX != 0) { + mInterpolatorX = AnimationUtils.loadInterpolator(context, interpolatorX); + } else { + mInterpolatorX = TransitionConstants.LINEAR_OUT_SLOW_IN; + } + + final int interpolatorY = a.getResourceId( + R.styleable.EpicenterTranslateClipReveal_interpolatorY, 0); + if (interpolatorY != 0) { + mInterpolatorY = AnimationUtils.loadInterpolator(context, interpolatorY); + } else { + mInterpolatorY = TransitionConstants.FAST_OUT_SLOW_IN; + } + + final int interpolatorZ = a.getResourceId( + R.styleable.EpicenterTranslateClipReveal_interpolatorZ, 0); + if (interpolatorZ != 0) { + mInterpolatorZ = AnimationUtils.loadInterpolator(context, interpolatorZ); + } else { + mInterpolatorZ = TransitionConstants.FAST_OUT_SLOW_IN; + } + + a.recycle(); + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + super.captureStartValues(transitionValues); + captureValues(transitionValues); + } + + @Override + public void captureEndValues(TransitionValues transitionValues) { + super.captureEndValues(transitionValues); + captureValues(transitionValues); + } + + private void captureValues(TransitionValues values) { + final View view = values.view; + if (view.getVisibility() == View.GONE) { + return; + } + + final Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); + values.values.put(PROPNAME_BOUNDS, bounds); + values.values.put(PROPNAME_TRANSLATE_X, view.getTranslationX()); + values.values.put(PROPNAME_TRANSLATE_Y, view.getTranslationY()); + values.values.put(PROPNAME_TRANSLATE_Z, view.getTranslationZ()); + values.values.put(PROPNAME_Z, view.getZ()); + + final Rect clip = view.getClipBounds(); + values.values.put(PROPNAME_CLIP, clip); + } + + @Override + public Animator onAppear(ViewGroup sceneRoot, View view, + TransitionValues startValues, TransitionValues endValues) { + if (endValues == null) { + return null; + } + + final Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS); + final Rect startBounds = getEpicenterOrCenter(endBounds); + final float startX = startBounds.centerX() - endBounds.centerX(); + final float startY = startBounds.centerY() - endBounds.centerY(); + final float startZ = 0 - (float) endValues.values.get(PROPNAME_Z); + + // Translate the view to be centered on the epicenter. + view.setTranslationX(startX); + view.setTranslationY(startY); + view.setTranslationZ(startZ); + + final float endX = (float) endValues.values.get(PROPNAME_TRANSLATE_X); + final float endY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y); + final float endZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z); + + final Rect endClip = getBestRect(endValues); + final Rect startClip = getEpicenterOrCenter(endClip); + + // Prepare the view. + view.setClipBounds(startClip); + + final State startStateX = new State(startClip.left, startClip.right, startX); + final State endStateX = new State(endClip.left, endClip.right, endX); + final State startStateY = new State(startClip.top, startClip.bottom, startY); + final State endStateY = new State(endClip.top, endClip.bottom, endY); + + return createRectAnimator(view, startStateX, startStateY, startZ, endStateX, endStateY, + endZ, endValues, mInterpolatorX, mInterpolatorY, mInterpolatorZ); + } + + @Override + public Animator onDisappear(ViewGroup sceneRoot, View view, + TransitionValues startValues, TransitionValues endValues) { + if (startValues == null) { + return null; + } + + final Rect startBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS); + final Rect endBounds = getEpicenterOrCenter(startBounds); + final float endX = endBounds.centerX() - startBounds.centerX(); + final float endY = endBounds.centerY() - startBounds.centerY(); + final float endZ = 0 - (float) startValues.values.get(PROPNAME_Z); + + final float startX = (float) endValues.values.get(PROPNAME_TRANSLATE_X); + final float startY = (float) endValues.values.get(PROPNAME_TRANSLATE_Y); + final float startZ = (float) endValues.values.get(PROPNAME_TRANSLATE_Z); + + final Rect startClip = getBestRect(startValues); + final Rect endClip = getEpicenterOrCenter(startClip); + + // Prepare the view. + view.setClipBounds(startClip); + + final State startStateX = new State(startClip.left, startClip.right, startX); + final State endStateX = new State(endClip.left, endClip.right, endX); + final State startStateY = new State(startClip.top, startClip.bottom, startY); + final State endStateY = new State(endClip.top, endClip.bottom, endY); + + return createRectAnimator(view, startStateX, startStateY, startZ, endStateX, endStateY, + endZ, endValues, mInterpolatorX, mInterpolatorY, mInterpolatorZ); + } + + private Rect getEpicenterOrCenter(Rect bestRect) { + final Rect epicenter = getEpicenter(); + if (epicenter != null) { + return epicenter; + } + + final int centerX = bestRect.centerX(); + final int centerY = bestRect.centerY(); + return new Rect(centerX, centerY, centerX, centerY); + } + + private Rect getBestRect(TransitionValues values) { + final Rect clipRect = (Rect) values.values.get(PROPNAME_CLIP); + if (clipRect == null) { + return (Rect) values.values.get(PROPNAME_BOUNDS); + } + return clipRect; + } + + private static Animator createRectAnimator(final View view, State startX, State startY, + float startZ, State endX, State endY, float endZ, TransitionValues endValues, + TimeInterpolator interpolatorX, TimeInterpolator interpolatorY, + TimeInterpolator interpolatorZ) { + final StateEvaluator evaluator = new StateEvaluator(); + + final ObjectAnimator animZ = ObjectAnimator.ofFloat(view, View.TRANSLATION_Z, startZ, endZ); + if (interpolatorZ != null) { + animZ.setInterpolator(interpolatorZ); + } + + final StateProperty propX = new StateProperty(StateProperty.TARGET_X); + final ObjectAnimator animX = ObjectAnimator.ofObject(view, propX, evaluator, startX, endX); + if (interpolatorX != null) { + animX.setInterpolator(interpolatorX); + } + + final StateProperty propY = new StateProperty(StateProperty.TARGET_Y); + final ObjectAnimator animY = ObjectAnimator.ofObject(view, propY, evaluator, startY, endY); + if (interpolatorY != null) { + animY.setInterpolator(interpolatorY); + } + + final Rect terminalClip = (Rect) endValues.values.get(PROPNAME_CLIP); + final AnimatorListenerAdapter animatorListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setClipBounds(terminalClip); + } + }; + + final AnimatorSet animSet = new AnimatorSet(); + animSet.playTogether(animX, animY, animZ); + animSet.addListener(animatorListener); + return animSet; + } + + private static class State { + int lower; + int upper; + float trans; + + public State() {} + + public State(int lower, int upper, float trans) { + this.lower = lower; + this.upper = upper; + this.trans = trans; + } + } + + private static class StateEvaluator implements TypeEvaluator<State> { + private final State mTemp = new State(); + + @Override + public State evaluate(float fraction, State startValue, State endValue) { + mTemp.upper = startValue.upper + (int) ((endValue.upper - startValue.upper) * fraction); + mTemp.lower = startValue.lower + (int) ((endValue.lower - startValue.lower) * fraction); + mTemp.trans = startValue.trans + (int) ((endValue.trans - startValue.trans) * fraction); + return mTemp; + } + } + + private static class StateProperty extends Property<View, State> { + public static final char TARGET_X = 'x'; + public static final char TARGET_Y = 'y'; + + private final Rect mTempRect = new Rect(); + private final State mTempState = new State(); + + private final int mTargetDimension; + + public StateProperty(char targetDimension) { + super(State.class, "state_" + targetDimension); + + mTargetDimension = targetDimension; + } + + @Override + public State get(View object) { + final Rect tempRect = mTempRect; + if (!object.getClipBounds(tempRect)) { + tempRect.setEmpty(); + } + final State tempState = mTempState; + if (mTargetDimension == TARGET_X) { + tempState.trans = object.getTranslationX(); + tempState.lower = tempRect.left + (int) tempState.trans; + tempState.upper = tempRect.right + (int) tempState.trans; + } else { + tempState.trans = object.getTranslationY(); + tempState.lower = tempRect.top + (int) tempState.trans; + tempState.upper = tempRect.bottom + (int) tempState.trans; + } + return tempState; + } + + @Override + public void set(View object, State value) { + final Rect tempRect = mTempRect; + if (object.getClipBounds(tempRect)) { + if (mTargetDimension == TARGET_X) { + tempRect.left = value.lower - (int) value.trans; + tempRect.right = value.upper - (int) value.trans; + } else { + tempRect.top = value.lower - (int) value.trans; + tempRect.bottom = value.upper - (int) value.trans; + } + object.setClipBounds(tempRect); + } + + if (mTargetDimension == TARGET_X) { + object.setTranslationX(value.trans); + } else { + object.setTranslationY(value.trans); + } + } + } +} diff --git a/core/java/com/android/internal/util/AsyncChannel.java b/core/java/com/android/internal/util/AsyncChannel.java index 34f62ba..bd0e6ce 100644 --- a/core/java/com/android/internal/util/AsyncChannel.java +++ b/core/java/com/android/internal/util/AsyncChannel.java @@ -462,10 +462,8 @@ public class AsyncChannel { } catch(Exception e) { } // Tell source we're disconnected. - if (mSrcHandler != null) { - replyDisconnected(STATUS_SUCCESSFUL); - mSrcHandler = null; - } + replyDisconnected(STATUS_SUCCESSFUL); + mSrcHandler = null; // Unlink only when bindService isn't used if (mConnection == null && mDstMessenger != null && mDeathMonitor!= null) { mDstMessenger.getBinder().unlinkToDeath(mDeathMonitor, 0); @@ -871,6 +869,8 @@ public class AsyncChannel { * @param status to be stored in msg.arg1 */ private void replyDisconnected(int status) { + // Can't reply if already disconnected. Avoid NullPointerException. + if (mSrcHandler == null) return; Message msg = mSrcHandler.obtainMessage(CMD_CHANNEL_DISCONNECTED); msg.arg1 = status; msg.obj = this; diff --git a/core/java/com/android/internal/util/CallbackRegistry.java b/core/java/com/android/internal/util/CallbackRegistry.java new file mode 100644 index 0000000..0f228d4 --- /dev/null +++ b/core/java/com/android/internal/util/CallbackRegistry.java @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.util; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tracks callbacks for the event. This class supports reentrant modification + * of the callbacks during notification without adversely disrupting notifications. + * A common pattern for callbacks is to receive a notification and then remove + * themselves. This class handles this behavior with constant memory under + * most circumstances. + * + * <p>A subclass of {@link CallbackRegistry.NotifierCallback} must be passed to + * the constructor to define how notifications should be called. That implementation + * does the actual notification on the listener.</p> + * + * <p>This class supports only callbacks with at most two parameters. + * Typically, these are the notification originator and a parameter, but these may + * be used as required. If more than two parameters are required or primitive types + * must be used, <code>A</code> should be some kind of containing structure that + * the subclass may reuse between notifications.</p> + * + * @param <C> The callback type. + * @param <T> The notification sender type. Typically this is the containing class. + * @param <A> Opaque argument used to pass additional data beyond an int. + */ +public class CallbackRegistry<C, T, A> implements Cloneable { + private static final String TAG = "CallbackRegistry"; + + /** An ordered collection of listeners waiting to be notified. */ + private List<C> mCallbacks = new ArrayList<C>(); + + /** + * A bit flag for the first 64 listeners that are removed during notification. + * The lowest significant bit corresponds to the 0th index into mCallbacks. + * For a small number of callbacks, no additional array of objects needs to + * be allocated. + */ + private long mFirst64Removed = 0x0; + + /** + * Bit flags for the remaining callbacks that are removed during notification. + * When there are more than 64 callbacks and one is marked for removal, a dynamic + * array of bits are allocated for the callbacks. + */ + private long[] mRemainderRemoved; + + /** + * The reentrancy level of the notification. When we notify a callback, it may cause + * further notifications. The reentrancy level must be tracked to let us clean up + * the callback state when all notifications have been processed. + */ + private int mNotificationLevel; + + /** The notification mechanism for notifying an event. */ + private final NotifierCallback<C, T, A> mNotifier; + + /** + * Creates an EventRegistry that notifies the event with notifier. + * @param notifier The class to use to notify events. + */ + public CallbackRegistry(NotifierCallback<C, T, A> notifier) { + mNotifier = notifier; + } + + /** + * Notify all callbacks. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + */ + public synchronized void notifyCallbacks(T sender, int arg, A arg2) { + mNotificationLevel++; + notifyRecurseLocked(sender, arg, arg2); + mNotificationLevel--; + if (mNotificationLevel == 0) { + if (mRemainderRemoved != null) { + for (int i = mRemainderRemoved.length - 1; i >= 0; i--) { + final long removedBits = mRemainderRemoved[i]; + if (removedBits != 0) { + removeRemovedCallbacks((i + 1) * Long.SIZE, removedBits); + mRemainderRemoved[i] = 0; + } + } + } + if (mFirst64Removed != 0) { + removeRemovedCallbacks(0, mFirst64Removed); + mFirst64Removed = 0; + } + } + } + + /** + * Notify up to the first Long.SIZE callbacks that don't have a bit set in <code>removed</code>. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + */ + private void notifyFirst64Locked(T sender, int arg, A arg2) { + final int maxNotified = Math.min(Long.SIZE, mCallbacks.size()); + notifyCallbacksLocked(sender, arg, arg2, 0, maxNotified, mFirst64Removed); + } + + /** + * Notify all callbacks using a recursive algorithm to avoid allocating on the heap. + * This part captures the callbacks beyond Long.SIZE that have no bits allocated for + * removal before it recurses into {@link #notifyRemainderLocked(Object, int, A, int)}. + * <p> + * Recursion is used to avoid allocating temporary state on the heap. Each stack has one + * long (64 callbacks) worth of information of which has been removed. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + */ + private void notifyRecurseLocked(T sender, int arg, A arg2) { + final int callbackCount = mCallbacks.size(); + final int remainderIndex = mRemainderRemoved == null ? -1 : mRemainderRemoved.length - 1; + + // Now we've got all callbacks that have no mRemainderRemoved value, so notify the + // others. + notifyRemainderLocked(sender, arg, arg2, remainderIndex); + + // notifyRemainderLocked notifies all at maxIndex, so we'd normally start at maxIndex + 1 + // However, we must also keep track of those in mFirst64Removed, so we add 2 instead: + final int startCallbackIndex = (remainderIndex + 2) * Long.SIZE; + + // The remaining have no bit set + notifyCallbacksLocked(sender, arg, arg2, startCallbackIndex, callbackCount, 0); + } + + /** + * Notify callbacks that have mRemainderRemoved bits set for remainderIndex. If + * remainderIndex is -1, the first 64 will be notified instead. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param remainderIndex The index into mRemainderRemoved that should be notified. + */ + private void notifyRemainderLocked(T sender, int arg, A arg2, int remainderIndex) { + if (remainderIndex < 0) { + notifyFirst64Locked(sender, arg, arg2); + } else { + final long bits = mRemainderRemoved[remainderIndex]; + final int startIndex = (remainderIndex + 1) * Long.SIZE; + final int endIndex = Math.min(mCallbacks.size(), startIndex + Long.SIZE); + notifyRemainderLocked(sender, arg, arg2, remainderIndex - 1); + notifyCallbacksLocked(sender, arg, arg2, startIndex, endIndex, bits); + } + } + + /** + * Notify callbacks from startIndex to endIndex, using bits as the bit status + * for whether they have been removed or not. bits should be from mRemainderRemoved or + * mFirst64Removed. bits set to 0 indicates that all callbacks from startIndex to + * endIndex should be notified. + * + * @param sender The originator. This is an opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param arg2 An opaque parameter passed to + * {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, A)} + * @param startIndex The index into the mCallbacks to start notifying. + * @param endIndex One past the last index into mCallbacks to notify. + * @param bits A bit field indicating which callbacks have been removed and shouldn't + * be notified. + */ + private void notifyCallbacksLocked(T sender, int arg, A arg2, final int startIndex, + final int endIndex, final long bits) { + long bitMask = 1; + for (int i = startIndex; i < endIndex; i++) { + if ((bits & bitMask) == 0) { + mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2); + } + bitMask <<= 1; + } + } + + /** + * Add a callback to be notified. If the callback is already in the list, another won't + * be added. This does not affect current notifications. + * @param callback The callback to add. + */ + public synchronized void add(C callback) { + int index = mCallbacks.lastIndexOf(callback); + if (index < 0 || isRemovedLocked(index)) { + mCallbacks.add(callback); + } + } + + /** + * Returns true if the callback at index has been marked for removal. + * + * @param index The index into mCallbacks to check. + * @return true if the callback at index has been marked for removal. + */ + private boolean isRemovedLocked(int index) { + if (index < Long.SIZE) { + // It is in the first 64 callbacks, just check the bit. + final long bitMask = 1L << index; + return (mFirst64Removed & bitMask) != 0; + } else if (mRemainderRemoved == null) { + // It is after the first 64 callbacks, but nothing else was marked for removal. + return false; + } else { + final int maskIndex = (index / Long.SIZE) - 1; + if (maskIndex >= mRemainderRemoved.length) { + // There are some items in mRemainderRemoved, but nothing at the given index. + return false; + } else { + // There is something marked for removal, so we have to check the bit. + final long bits = mRemainderRemoved[maskIndex]; + final long bitMask = 1L << (index % Long.SIZE); + return (bits & bitMask) != 0; + } + } + } + + /** + * Removes callbacks from startIndex to startIndex + Long.SIZE, based + * on the bits set in removed. + * @param startIndex The index into the mCallbacks to start removing callbacks. + * @param removed The bits indicating removal, where each bit is set for one callback + * to be removed. + */ + private void removeRemovedCallbacks(int startIndex, long removed) { + // The naive approach should be fine. There may be a better bit-twiddling approach. + final int endIndex = startIndex + Long.SIZE; + + long bitMask = 1L << (Long.SIZE - 1); + for (int i = endIndex - 1; i >= startIndex; i--) { + if ((removed & bitMask) != 0) { + mCallbacks.remove(i); + } + bitMask >>>= 1; + } + } + + /** + * Remove a callback. This callback won't be notified after this call completes. + * @param callback The callback to remove. + */ + public synchronized void remove(C callback) { + if (mNotificationLevel == 0) { + mCallbacks.remove(callback); + } else { + int index = mCallbacks.lastIndexOf(callback); + if (index >= 0) { + setRemovalBitLocked(index); + } + } + } + + private void setRemovalBitLocked(int index) { + if (index < Long.SIZE) { + // It is in the first 64 callbacks, just check the bit. + final long bitMask = 1L << index; + mFirst64Removed |= bitMask; + } else { + final int remainderIndex = (index / Long.SIZE) - 1; + if (mRemainderRemoved == null) { + mRemainderRemoved = new long[mCallbacks.size() / Long.SIZE]; + } else if (mRemainderRemoved.length < remainderIndex) { + // need to make it bigger + long[] newRemainders = new long[mCallbacks.size() / Long.SIZE]; + System.arraycopy(mRemainderRemoved, 0, newRemainders, 0, mRemainderRemoved.length); + mRemainderRemoved = newRemainders; + } + final long bitMask = 1L << (index % Long.SIZE); + mRemainderRemoved[remainderIndex] |= bitMask; + } + } + + /** + * Makes a copy of the registered callbacks and returns it. + * + * @return a copy of the registered callbacks. + */ + public synchronized ArrayList<C> copyListeners() { + ArrayList<C> callbacks = new ArrayList<C>(mCallbacks.size()); + int numListeners = mCallbacks.size(); + for (int i = 0; i < numListeners; i++) { + if (!isRemovedLocked(i)) { + callbacks.add(mCallbacks.get(i)); + } + } + return callbacks; + } + + /** + * Returns true if there are no registered callbacks or false otherwise. + * + * @return true if there are no registered callbacks or false otherwise. + */ + public synchronized boolean isEmpty() { + if (mCallbacks.isEmpty()) { + return true; + } else if (mNotificationLevel == 0) { + return false; + } else { + int numListeners = mCallbacks.size(); + for (int i = 0; i < numListeners; i++) { + if (!isRemovedLocked(i)) { + return false; + } + } + return true; + } + } + + /** + * Removes all callbacks from the list. + */ + public synchronized void clear() { + if (mNotificationLevel == 0) { + mCallbacks.clear(); + } else if (!mCallbacks.isEmpty()) { + for (int i = mCallbacks.size() - 1; i >= 0; i--) { + setRemovalBitLocked(i); + } + } + } + + public synchronized CallbackRegistry<C, T, A> clone() { + CallbackRegistry<C, T, A> clone = null; + try { + clone = (CallbackRegistry<C, T, A>) super.clone(); + clone.mFirst64Removed = 0; + clone.mRemainderRemoved = null; + clone.mNotificationLevel = 0; + clone.mCallbacks = new ArrayList<C>(); + final int numListeners = mCallbacks.size(); + for (int i = 0; i < numListeners; i++) { + if (!isRemovedLocked(i)) { + clone.mCallbacks.add(mCallbacks.get(i)); + } + } + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + return clone; + } + + /** + * Class used to notify events from CallbackRegistry. + * + * @param <C> The callback type. + * @param <T> The notification sender type. Typically this is the containing class. + * @param <A> An opaque argument to pass to the notifier + */ + public abstract static class NotifierCallback<C, T, A> { + /** + * Used to notify the callback. + * + * @param callback The callback to notify. + * @param sender The opaque sender object. + * @param arg The opaque notification parameter. + * @param arg2 An opaque argument passed in + * {@link CallbackRegistry#notifyCallbacks} + * @see CallbackRegistry#CallbackRegistry(CallbackRegistry.NotifierCallback) + */ + public abstract void onNotifyCallback(C callback, T sender, int arg, A arg2); + } +} diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 6f104dd..60c5e42 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -59,7 +59,8 @@ interface IInputMethodManager { int controlFlags, int softInputMode, int windowFlags, in EditorInfo attribute, IInputContext inputContext); - void showInputMethodPickerFromClient(in IInputMethodClient client); + void showInputMethodPickerFromClient(in IInputMethodClient client, + int auxiliarySubtypeMode); void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId); void setInputMethod(in IBinder token, String id); void setInputMethodAndSubtype(in IBinder token, String id, in InputMethodSubtype subtype); diff --git a/core/java/com/android/internal/widget/ButtonBarLayout.java b/core/java/com/android/internal/widget/ButtonBarLayout.java index 64e6c69..f58ab03 100644 --- a/core/java/com/android/internal/widget/ButtonBarLayout.java +++ b/core/java/com/android/internal/widget/ButtonBarLayout.java @@ -17,6 +17,7 @@ package com.android.internal.widget; import android.content.Context; +import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; @@ -29,37 +30,39 @@ import com.android.internal.R; * orientation when it can't fit its child views horizontally. */ public class ButtonBarLayout extends LinearLayout { - /** Spacer used in horizontal orientation. */ - private final View mSpacer; - /** Whether the current configuration allows stacking. */ - private final boolean mAllowStacked; + private final boolean mAllowStacking; /** Whether the layout is currently stacked. */ private boolean mStacked; + private int mLastWidthSize = -1; + public ButtonBarLayout(Context context, AttributeSet attrs) { super(context, attrs); - mAllowStacked = context.getResources().getBoolean(R.bool.allow_stacked_button_bar); - mSpacer = findViewById(R.id.spacer); + final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout); + mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, false); + ta.recycle(); + + mStacked = getOrientation() == VERTICAL; } @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mAllowStacking) { + final int widthSize = MeasureSpec.getSize(widthMeasureSpec); + if (widthSize > mLastWidthSize && mStacked) { + // We're being measured wider this time, try un-stacking. + setStacked(false); + } - // Maybe we can fit the content now? - if (w > oldw && mStacked) { - setStacked(false); + mLastWidthSize = widthSize; } - } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (mAllowStacked && getOrientation() == LinearLayout.HORIZONTAL) { + if (mAllowStacking && !mStacked) { final int measuredWidth = getMeasuredWidthAndState(); final int measuredWidthState = measuredWidth & MEASURED_STATE_MASK; if (measuredWidthState == MEASURED_STATE_TOO_SMALL) { @@ -75,8 +78,9 @@ public class ButtonBarLayout extends LinearLayout { setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL); setGravity(stacked ? Gravity.RIGHT : Gravity.BOTTOM); - if (mSpacer != null) { - mSpacer.setVisibility(stacked ? View.GONE : View.INVISIBLE); + final View spacer = findViewById(R.id.spacer); + if (spacer != null) { + spacer.setVisibility(stacked ? View.GONE : View.INVISIBLE); } // Reverse the child order. This is specific to the Material button diff --git a/core/jni/Android.mk b/core/jni/Android.mk index bbdd860..5448214 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -132,6 +132,7 @@ LOCAL_SRC_FILES:= \ android_media_AudioRecord.cpp \ android_media_AudioSystem.cpp \ android_media_AudioTrack.cpp \ + android_media_DeviceCallback.cpp \ android_media_JetPlayer.cpp \ android_media_RemoteDisplay.cpp \ android_media_ToneGenerator.cpp \ diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 5c95f8a..2785c48 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -1,3 +1,7 @@ +#define LOG_TAG "Bitmap" + +#include "Bitmap.h" + #include "Paint.h" #include "SkBitmap.h" #include "SkPixelRef.h" @@ -14,11 +18,339 @@ #include "android_util_Binder.h" #include "android_nio_utils.h" #include "CreateJavaOutputStreamAdaptor.h" +#include <Caches.h> #include "core_jni_helpers.h" #include <jni.h> +namespace android { + +class WrappedPixelRef : public SkPixelRef { +public: + WrappedPixelRef(Bitmap* wrapper, void* storage, + const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable) + : SkPixelRef(info) + , mBitmap(*wrapper) + , mStorage(storage) { + reconfigure(info, rowBytes, ctable); + } + + ~WrappedPixelRef() { + // Tell SkRefCnt that everything is as it expects by forcing + // the refcnt to 1 + internal_dispose_restore_refcnt_to_1(); + SkSafeUnref(mColorTable); + } + + void reconfigure(const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable) { + if (kIndex_8_SkColorType != info.colorType()) { + ctable = nullptr; + } + mRowBytes = rowBytes; + if (mColorTable != ctable) { + SkSafeUnref(mColorTable); + mColorTable = ctable; + SkSafeRef(mColorTable); + } + // Dirty hack is dirty + // TODO: Figure something out here, Skia's current design makes this + // really hard to work with. Skia really, really wants immutable objects, + // but with the nested-ref-count hackery going on that's just not + // feasible without going insane trying to figure it out + SkImageInfo* myInfo = const_cast<SkImageInfo*>(&this->info()); + *myInfo = info; + + // Docs say to only call this in the ctor, but we're going to call + // it anyway even if this isn't always the ctor. + // TODO: Fix this too as part of the above TODO + setPreLocked(mStorage, mRowBytes, mColorTable); + } + + // Can't mark as override since SkPixelRef::rowBytes isn't virtual + // but that's OK since we just want BitmapWrapper to be able to rely + // on calling rowBytes() on an unlocked pixelref, which it will be + // doing on a WrappedPixelRef type, not a SkPixelRef, so static + // dispatching will do what we want. + size_t rowBytes() const { return mRowBytes; } + SkColorTable* colorTable() const { return mColorTable; } + + bool hasHardwareMipMap() const { + return mHasHardwareMipMap; + } + + void setHasHardwareMipMap(bool hasMipMap) { + mHasHardwareMipMap = hasMipMap; + } + +protected: + virtual bool onNewLockPixels(LockRec* rec) override { + rec->fPixels = mStorage; + rec->fRowBytes = mRowBytes; + rec->fColorTable = mColorTable; + return true; + } + + virtual void onUnlockPixels() override { + // nothing + } + + virtual size_t getAllocatedSizeInBytes() const override { + return info().getSafeSize(mRowBytes); + } + +private: + Bitmap& mBitmap; + void* mStorage; + size_t mRowBytes = 0; + SkColorTable* mColorTable = nullptr; + bool mHasHardwareMipMap = false; + + virtual void internal_dispose() const override { + mBitmap.onStrongRefDestroyed(); + } +}; + +Bitmap::Bitmap(JNIEnv* env, jbyteArray storageObj, void* address, + const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable) + : mPixelStorageType(PixelStorageType::Java) { + env->GetJavaVM(&mPixelStorage.java.jvm); + mPixelStorage.java.jweakRef = env->NewWeakGlobalRef(storageObj); + mPixelStorage.java.jstrongRef = nullptr; + mPixelRef.reset(new WrappedPixelRef(this, address, info, rowBytes, ctable)); + // Note: this will trigger a call to onStrongRefDestroyed(), but + // we want the pixel ref to have a ref count of 0 at this point + mPixelRef->unref(); +} + +Bitmap::Bitmap(void* address, void* context, FreeFunc freeFunc, + const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable) + : mPixelStorageType(PixelStorageType::External) { + mPixelStorage.external.address = address; + mPixelStorage.external.context = context; + mPixelStorage.external.freeFunc = freeFunc; + mPixelRef.reset(new WrappedPixelRef(this, address, info, rowBytes, ctable)); + // Note: this will trigger a call to onStrongRefDestroyed(), but + // we want the pixel ref to have a ref count of 0 at this point + mPixelRef->unref(); +} + +Bitmap::~Bitmap() { + doFreePixels(); +} + +void Bitmap::freePixels() { + AutoMutex _lock(mLock); + if (mPinnedRefCount == 0) { + doFreePixels(); + mPixelStorageType = PixelStorageType::Invalid; + } +} + +void Bitmap::doFreePixels() { + switch (mPixelStorageType) { + case PixelStorageType::Invalid: + // already free'd, nothing to do + break; + case PixelStorageType::External: + mPixelStorage.external.freeFunc(mPixelStorage.external.address, + mPixelStorage.external.context); + break; + case PixelStorageType::Java: + JNIEnv* env = jniEnv(); + LOG_ALWAYS_FATAL_IF(mPixelStorage.java.jstrongRef, + "Deleting a bitmap wrapper while there are outstanding strong " + "references! mPinnedRefCount = %d", mPinnedRefCount); + env->DeleteWeakGlobalRef(mPixelStorage.java.jweakRef); + break; + } + + if (android::uirenderer::Caches::hasInstance()) { + android::uirenderer::Caches::getInstance().textureCache.releaseTexture( + mPixelRef->getStableID()); + } +} + +bool Bitmap::hasHardwareMipMap() { + return mPixelRef->hasHardwareMipMap(); +} + +void Bitmap::setHasHardwareMipMap(bool hasMipMap) { + mPixelRef->setHasHardwareMipMap(hasMipMap); +} + +const SkImageInfo& Bitmap::info() const { + assertValid(); + return mPixelRef->info(); +} + +size_t Bitmap::rowBytes() const { + return mPixelRef->rowBytes(); +} + +SkPixelRef* Bitmap::peekAtPixelRef() const { + assertValid(); + return mPixelRef.get(); +} + +SkPixelRef* Bitmap::refPixelRef() { + assertValid(); + android::AutoMutex _lock(mLock); + return refPixelRefLocked(); +} + +SkPixelRef* Bitmap::refPixelRefLocked() { + mPixelRef->ref(); + if (mPixelRef->unique()) { + // We just restored this from 0, pin the pixels and inc the strong count + // Note that there *might be* an incoming onStrongRefDestroyed from whatever + // last unref'd + pinPixelsLocked(); + mPinnedRefCount++; + } + return mPixelRef.get(); +} + +void Bitmap::reconfigure(const SkImageInfo& info, size_t rowBytes, + SkColorTable* ctable) { + { + android::AutoMutex _lock(mLock); + if (mPinnedRefCount) { + ALOGW("Called reconfigure on a bitmap that is in use! This may" + " cause graphical corruption!"); + } + } + mPixelRef->reconfigure(info, rowBytes, ctable); +} + +void Bitmap::reconfigure(const SkImageInfo& info) { + reconfigure(info, info.minRowBytes(), nullptr); +} + +void Bitmap::detachFromJava() { + bool disposeSelf; + { + android::AutoMutex _lock(mLock); + mAttachedToJava = false; + disposeSelf = shouldDisposeSelfLocked(); + } + if (disposeSelf) { + delete this; + } +} + +bool Bitmap::shouldDisposeSelfLocked() { + return mPinnedRefCount == 0 && !mAttachedToJava; +} + +JNIEnv* Bitmap::jniEnv() { + JNIEnv* env; + auto success = mPixelStorage.java.jvm->GetEnv((void**)&env, JNI_VERSION_1_6); + LOG_ALWAYS_FATAL_IF(success != JNI_OK, + "Failed to get JNIEnv* from JVM: %p", mPixelStorage.java.jvm); + return env; +} + +void Bitmap::onStrongRefDestroyed() { + bool disposeSelf = false; + { + android::AutoMutex _lock(mLock); + if (mPinnedRefCount > 0) { + mPinnedRefCount--; + if (mPinnedRefCount == 0) { + unpinPixelsLocked(); + disposeSelf = shouldDisposeSelfLocked(); + } + } + } + if (disposeSelf) { + delete this; + } +} + +void Bitmap::pinPixelsLocked() { + switch (mPixelStorageType) { + case PixelStorageType::Invalid: + LOG_ALWAYS_FATAL("Cannot pin invalid pixels!"); + break; + case PixelStorageType::External: + // Nothing to do + break; + case PixelStorageType::Java: { + JNIEnv* env = jniEnv(); + if (!mPixelStorage.java.jstrongRef) { + mPixelStorage.java.jstrongRef = reinterpret_cast<jbyteArray>( + env->NewGlobalRef(mPixelStorage.java.jweakRef)); + if (!mPixelStorage.java.jstrongRef) { + LOG_ALWAYS_FATAL("Failed to acquire strong reference to pixels"); + } + } + break; + } + } +} + +void Bitmap::unpinPixelsLocked() { + switch (mPixelStorageType) { + case PixelStorageType::Invalid: + LOG_ALWAYS_FATAL("Cannot unpin invalid pixels!"); + break; + case PixelStorageType::External: + // Don't need to do anything + break; + case PixelStorageType::Java: { + JNIEnv* env = jniEnv(); + if (mPixelStorage.java.jstrongRef) { + env->DeleteGlobalRef(mPixelStorage.java.jstrongRef); + mPixelStorage.java.jstrongRef = nullptr; + } + break; + } + } +} + +void Bitmap::getSkBitmap(SkBitmap* outBitmap) { + assertValid(); + android::AutoMutex _lock(mLock); + // Safe because mPixelRef is a WrappedPixelRef type, otherwise rowBytes() + // would require locking the pixels first. + outBitmap->setInfo(mPixelRef->info(), mPixelRef->rowBytes()); + outBitmap->setPixelRef(refPixelRefLocked())->unref(); + outBitmap->setHasHardwareMipMap(hasHardwareMipMap()); +} + +void Bitmap::assertValid() const { + LOG_ALWAYS_FATAL_IF(mPixelStorageType == PixelStorageType::Invalid, + "Error, cannot access an invalid/free'd bitmap here!"); +} + +} // namespace android + +using namespace android; + +// Convenience class that does not take a global ref on the pixels, relying +// on the caller already having a local JNI ref +class LocalScopedBitmap { +public: + LocalScopedBitmap(jlong bitmapHandle) + : mBitmap(reinterpret_cast<Bitmap*>(bitmapHandle)) {} + + Bitmap* operator->() { + return mBitmap; + } + + void* pixels() { + return mBitmap->peekAtPixelRef()->pixels(); + } + + bool valid() { + return mBitmap && mBitmap->valid(); + } + +private: + Bitmap* mBitmap; +}; + /////////////////////////////////////////////////////////////////////////////// // Conversions to/from SkColor, for get/setPixels, and the create method, which // is basically like setPixels @@ -328,8 +660,8 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, SkBitmap bitmap; bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType)); - jbyteArray buff = GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL); - if (NULL == buff) { + Bitmap* nativeBitmap = GraphicsJNI::allocateJavaPixelRef(env, &bitmap, NULL); + if (!nativeBitmap) { return NULL; } @@ -338,39 +670,41 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, 0, 0, width, height, bitmap); } - return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), buff, - getPremulBitmapCreateFlags(isMutable), NULL, NULL); + return GraphicsJNI::createBitmap(env, nativeBitmap, + getPremulBitmapCreateFlags(isMutable)); } static jobject Bitmap_copy(JNIEnv* env, jobject, jlong srcHandle, jint dstConfigHandle, jboolean isMutable) { - const SkBitmap* src = reinterpret_cast<SkBitmap*>(srcHandle); + SkBitmap src; + reinterpret_cast<Bitmap*>(srcHandle)->getSkBitmap(&src); SkColorType dstCT = GraphicsJNI::legacyBitmapConfigToColorType(dstConfigHandle); SkBitmap result; JavaPixelAllocator allocator(env); - if (!src->copyTo(&result, dstCT, &allocator)) { + if (!src.copyTo(&result, dstCT, &allocator)) { return NULL; } - return GraphicsJNI::createBitmap(env, new SkBitmap(result), allocator.getStorageObj(), - getPremulBitmapCreateFlags(isMutable), NULL, NULL); + Bitmap* bitmap = allocator.getStorageObjAndReset(); + return GraphicsJNI::createBitmap(env, bitmap, + getPremulBitmapCreateFlags(isMutable)); } static void Bitmap_destructor(JNIEnv* env, jobject, jlong bitmapHandle) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); - delete bitmap; + LocalScopedBitmap bitmap(bitmapHandle); + bitmap->detachFromJava(); } static jboolean Bitmap_recycle(JNIEnv* env, jobject, jlong bitmapHandle) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); - bitmap->setPixels(NULL, NULL); + LocalScopedBitmap bitmap(bitmapHandle); + bitmap->freePixels(); return JNI_TRUE; } static void Bitmap_reconfigure(JNIEnv* env, jobject clazz, jlong bitmapHandle, jint width, jint height, jint configHandle, jint allocSize, jboolean requestPremul) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); + LocalScopedBitmap bitmap(bitmapHandle); SkColorType colorType = GraphicsJNI::legacyBitmapConfigToColorType(configHandle); // ARGB_4444 is a deprecated format, convert automatically to 8888 @@ -383,11 +717,9 @@ static void Bitmap_reconfigure(JNIEnv* env, jobject clazz, jlong bitmapHandle, doThrowIAE(env, "Bitmap not large enough to support new configuration"); return; } - SkPixelRef* ref = bitmap->pixelRef(); - ref->ref(); SkAlphaType alphaType; - if (bitmap->colorType() != kRGB_565_SkColorType - && bitmap->alphaType() == kOpaque_SkAlphaType) { + if (bitmap->info().colorType() != kRGB_565_SkColorType + && bitmap->info().alphaType() == kOpaque_SkAlphaType) { // If the original bitmap was set to opaque, keep that setting, unless it // was 565, which is required to be opaque. alphaType = kOpaque_SkAlphaType; @@ -395,22 +727,7 @@ static void Bitmap_reconfigure(JNIEnv* env, jobject clazz, jlong bitmapHandle, // Otherwise respect the premultiplied request. alphaType = requestPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType; } - bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType)); - // FIXME: Skia thinks of an SkPixelRef as having a constant SkImageInfo (except for - // its alphatype), so it would make more sense from Skia's perspective to create a - // new SkPixelRef. That said, libhwui uses the pointer to the SkPixelRef as a key - // for its cache, so it won't realize this is the same Java Bitmap. - SkImageInfo& info = const_cast<SkImageInfo&>(ref->info()); - // Use the updated from the SkBitmap, which may have corrected an invalid alphatype. - // (e.g. 565 non-opaque) - info = bitmap->info(); - bitmap->setPixelRef(ref); - - // notifyPixelsChanged will increment the generation ID even though the actual pixel data - // hasn't been touched. This signals the renderer that the bitmap (including width, height, - // colortype and alphatype) has changed. - ref->notifyPixelsChanged(); - ref->unref(); + bitmap->reconfigure(SkImageInfo::Make(width, height, colorType, alphaType)); } // These must match the int values in Bitmap.java @@ -423,7 +740,8 @@ enum JavaEncodeFormat { static jboolean Bitmap_compress(JNIEnv* env, jobject clazz, jlong bitmapHandle, jint format, jint quality, jobject jstream, jbyteArray jstorage) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); + + LocalScopedBitmap bitmap(bitmapHandle); SkImageEncoder::Type fm; switch (format) { @@ -440,92 +758,92 @@ static jboolean Bitmap_compress(JNIEnv* env, jobject clazz, jlong bitmapHandle, return JNI_FALSE; } - bool success = false; - if (NULL != bitmap) { - SkAutoLockPixels alp(*bitmap); + if (!bitmap.valid()) { + return JNI_FALSE; + } - if (NULL == bitmap->getPixels()) { - return JNI_FALSE; - } + bool success = false; - SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage); - if (NULL == strm) { - return JNI_FALSE; - } + std::unique_ptr<SkWStream> strm(CreateJavaOutputStreamAdaptor(env, jstream, jstorage)); + if (!strm.get()) { + return JNI_FALSE; + } - SkImageEncoder* encoder = SkImageEncoder::Create(fm); - if (NULL != encoder) { - success = encoder->encodeStream(strm, *bitmap, quality); - delete encoder; - } - delete strm; + std::unique_ptr<SkImageEncoder> encoder(SkImageEncoder::Create(fm)); + if (encoder.get()) { + SkBitmap skbitmap; + bitmap->getSkBitmap(&skbitmap); + success = encoder->encodeStream(strm.get(), skbitmap, quality); } return success ? JNI_TRUE : JNI_FALSE; } static void Bitmap_erase(JNIEnv* env, jobject, jlong bitmapHandle, jint color) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); - bitmap->eraseColor(color); + LocalScopedBitmap bitmap(bitmapHandle); + SkBitmap skBitmap; + bitmap->getSkBitmap(&skBitmap); + skBitmap.eraseColor(color); } static jint Bitmap_rowBytes(JNIEnv* env, jobject, jlong bitmapHandle) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); + LocalScopedBitmap bitmap(bitmapHandle); return static_cast<jint>(bitmap->rowBytes()); } static jint Bitmap_config(JNIEnv* env, jobject, jlong bitmapHandle) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); - return GraphicsJNI::colorTypeToLegacyBitmapConfig(bitmap->colorType()); + LocalScopedBitmap bitmap(bitmapHandle); + return GraphicsJNI::colorTypeToLegacyBitmapConfig(bitmap->info().colorType()); } static jint Bitmap_getGenerationId(JNIEnv* env, jobject, jlong bitmapHandle) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); - return static_cast<jint>(bitmap->getGenerationID()); + LocalScopedBitmap bitmap(bitmapHandle); + return static_cast<jint>(bitmap->peekAtPixelRef()->getGenerationID()); } static jboolean Bitmap_isPremultiplied(JNIEnv* env, jobject, jlong bitmapHandle) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); - if (bitmap->alphaType() == kPremul_SkAlphaType) { + LocalScopedBitmap bitmap(bitmapHandle); + if (bitmap->info().alphaType() == kPremul_SkAlphaType) { return JNI_TRUE; } return JNI_FALSE; } static jboolean Bitmap_hasAlpha(JNIEnv* env, jobject, jlong bitmapHandle) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); - return !bitmap->isOpaque() ? JNI_TRUE : JNI_FALSE; + LocalScopedBitmap bitmap(bitmapHandle); + return !bitmap->info().isOpaque() ? JNI_TRUE : JNI_FALSE; } static void Bitmap_setHasAlpha(JNIEnv* env, jobject, jlong bitmapHandle, jboolean hasAlpha, jboolean requestPremul) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); + LocalScopedBitmap bitmap(bitmapHandle); if (hasAlpha) { - bitmap->setAlphaType(requestPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType); + bitmap->peekAtPixelRef()->changeAlphaType( + requestPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType); } else { - bitmap->setAlphaType(kOpaque_SkAlphaType); + bitmap->peekAtPixelRef()->changeAlphaType(kOpaque_SkAlphaType); } } static void Bitmap_setPremultiplied(JNIEnv* env, jobject, jlong bitmapHandle, jboolean isPremul) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); - if (!bitmap->isOpaque()) { + LocalScopedBitmap bitmap(bitmapHandle); + if (!bitmap->info().isOpaque()) { if (isPremul) { - bitmap->setAlphaType(kPremul_SkAlphaType); + bitmap->peekAtPixelRef()->changeAlphaType(kPremul_SkAlphaType); } else { - bitmap->setAlphaType(kUnpremul_SkAlphaType); + bitmap->peekAtPixelRef()->changeAlphaType(kUnpremul_SkAlphaType); } } } static jboolean Bitmap_hasMipMap(JNIEnv* env, jobject, jlong bitmapHandle) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); + LocalScopedBitmap bitmap(bitmapHandle); return bitmap->hasHardwareMipMap() ? JNI_TRUE : JNI_FALSE; } static void Bitmap_setHasMipMap(JNIEnv* env, jobject, jlong bitmapHandle, jboolean hasMipMap) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); + LocalScopedBitmap bitmap(bitmapHandle); bitmap->setHasHardwareMipMap(hasMipMap); } @@ -580,8 +898,8 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { } } - jbyteArray buffer = GraphicsJNI::allocateJavaPixelRef(env, bitmap.get(), ctable); - if (NULL == buffer) { + android::Bitmap* nativeBitmap = GraphicsJNI::allocateJavaPixelRef(env, bitmap.get(), ctable); + if (!nativeBitmap) { SkSafeUnref(ctable); return NULL; } @@ -593,6 +911,7 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { android::Parcel::ReadableBlob blob; android::status_t status = p->readBlob(size, &blob); if (status) { + nativeBitmap->detachFromJava(); doThrowRE(env, "Could not read bitmap from parcel blob."); return NULL; } @@ -603,7 +922,7 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { blob.release(); - return GraphicsJNI::createBitmap(env, bitmap.release(), buffer, + return GraphicsJNI::createBitmap(env, nativeBitmap, getPremulBitmapCreateFlags(isMutable), NULL, NULL, density); } @@ -611,24 +930,25 @@ static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, jlong bitmapHandle, jboolean isMutable, jint density, jobject parcel) { - const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); if (parcel == NULL) { SkDebugf("------- writeToParcel null parcel\n"); return JNI_FALSE; } android::Parcel* p = android::parcelForJavaObject(env, parcel); + SkBitmap bitmap; + reinterpret_cast<Bitmap*>(bitmapHandle)->getSkBitmap(&bitmap); p->writeInt32(isMutable); - p->writeInt32(bitmap->colorType()); - p->writeInt32(bitmap->alphaType()); - p->writeInt32(bitmap->width()); - p->writeInt32(bitmap->height()); - p->writeInt32(bitmap->rowBytes()); + p->writeInt32(bitmap.colorType()); + p->writeInt32(bitmap.alphaType()); + p->writeInt32(bitmap.width()); + p->writeInt32(bitmap.height()); + p->writeInt32(bitmap.rowBytes()); p->writeInt32(density); - if (bitmap->colorType() == kIndex_8_SkColorType) { - SkColorTable* ctable = bitmap->getColorTable(); + if (bitmap.colorType() == kIndex_8_SkColorType) { + SkColorTable* ctable = bitmap.getColorTable(); if (ctable != NULL) { int count = ctable->count(); p->writeInt32(count); @@ -639,7 +959,7 @@ static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, } } - size_t size = bitmap->getSize(); + size_t size = bitmap.getSize(); android::Parcel::WritableBlob blob; android::status_t status = p->writeBlob(size, &blob); @@ -648,14 +968,14 @@ static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, return JNI_FALSE; } - bitmap->lockPixels(); - const void* pSrc = bitmap->getPixels(); + bitmap.lockPixels(); + const void* pSrc = bitmap.getPixels(); if (pSrc == NULL) { memset(blob.data(), 0, size); } else { memcpy(blob.data(), pSrc, size); } - bitmap->unlockPixels(); + bitmap.unlockPixels(); blob.release(); return JNI_TRUE; @@ -664,17 +984,17 @@ static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, static jobject Bitmap_extractAlpha(JNIEnv* env, jobject clazz, jlong srcHandle, jlong paintHandle, jintArray offsetXY) { - const SkBitmap* src = reinterpret_cast<SkBitmap*>(srcHandle); + SkBitmap src; + reinterpret_cast<Bitmap*>(srcHandle)->getSkBitmap(&src); const android::Paint* paint = reinterpret_cast<android::Paint*>(paintHandle); SkIPoint offset; - SkBitmap* dst = new SkBitmap; + SkBitmap dst; JavaPixelAllocator allocator(env); - src->extractAlpha(dst, paint, &allocator, &offset); + src.extractAlpha(&dst, paint, &allocator, &offset); // If Skia can't allocate pixels for destination bitmap, it resets // it, that is set its pixels buffer to NULL, and zero width and height. - if (dst->getPixels() == NULL && src->getPixels() != NULL) { - delete dst; + if (dst.getPixels() == NULL && src.getPixels() != NULL) { doThrowOOME(env, "failed to allocate pixels for alpha"); return NULL; } @@ -685,53 +1005,55 @@ static jobject Bitmap_extractAlpha(JNIEnv* env, jobject clazz, env->ReleaseIntArrayElements(offsetXY, array, 0); } - return GraphicsJNI::createBitmap(env, dst, allocator.getStorageObj(), - getPremulBitmapCreateFlags(true), NULL, NULL); + return GraphicsJNI::createBitmap(env, allocator.getStorageObjAndReset(), + getPremulBitmapCreateFlags(true)); } /////////////////////////////////////////////////////////////////////////////// static jint Bitmap_getPixel(JNIEnv* env, jobject, jlong bitmapHandle, jint x, jint y) { - const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); - SkAutoLockPixels alp(*bitmap); + SkBitmap bitmap; + reinterpret_cast<Bitmap*>(bitmapHandle)->getSkBitmap(&bitmap); + SkAutoLockPixels alp(bitmap); - ToColorProc proc = ChooseToColorProc(*bitmap); + ToColorProc proc = ChooseToColorProc(bitmap); if (NULL == proc) { return 0; } - const void* src = bitmap->getAddr(x, y); + const void* src = bitmap.getAddr(x, y); if (NULL == src) { return 0; } SkColor dst[1]; - proc(dst, src, 1, bitmap->getColorTable()); + proc(dst, src, 1, bitmap.getColorTable()); return static_cast<jint>(dst[0]); } static void Bitmap_getPixels(JNIEnv* env, jobject, jlong bitmapHandle, jintArray pixelArray, jint offset, jint stride, jint x, jint y, jint width, jint height) { - const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); - SkAutoLockPixels alp(*bitmap); + SkBitmap bitmap; + reinterpret_cast<Bitmap*>(bitmapHandle)->getSkBitmap(&bitmap); + SkAutoLockPixels alp(bitmap); - ToColorProc proc = ChooseToColorProc(*bitmap); + ToColorProc proc = ChooseToColorProc(bitmap); if (NULL == proc) { return; } - const void* src = bitmap->getAddr(x, y); + const void* src = bitmap.getAddr(x, y); if (NULL == src) { return; } - SkColorTable* ctable = bitmap->getColorTable(); + SkColorTable* ctable = bitmap.getColorTable(); jint* dst = env->GetIntArrayElements(pixelArray, NULL); SkColor* d = (SkColor*)dst + offset; while (--height >= 0) { proc(d, src, width, ctable); d += stride; - src = (void*)((const char*)src + bitmap->rowBytes()); + src = (void*)((const char*)src + bitmap.rowBytes()); } env->ReleaseIntArrayElements(pixelArray, dst, 0); } @@ -740,79 +1062,85 @@ static void Bitmap_getPixels(JNIEnv* env, jobject, jlong bitmapHandle, static void Bitmap_setPixel(JNIEnv* env, jobject, jlong bitmapHandle, jint x, jint y, jint colorHandle) { - const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); + SkBitmap bitmap; + reinterpret_cast<Bitmap*>(bitmapHandle)->getSkBitmap(&bitmap); SkColor color = static_cast<SkColor>(colorHandle); - SkAutoLockPixels alp(*bitmap); - if (NULL == bitmap->getPixels()) { + SkAutoLockPixels alp(bitmap); + if (NULL == bitmap.getPixels()) { return; } - FromColorProc proc = ChooseFromColorProc(*bitmap); + FromColorProc proc = ChooseFromColorProc(bitmap); if (NULL == proc) { return; } - proc(bitmap->getAddr(x, y), &color, 1, x, y); - bitmap->notifyPixelsChanged(); + proc(bitmap.getAddr(x, y), &color, 1, x, y); + bitmap.notifyPixelsChanged(); } static void Bitmap_setPixels(JNIEnv* env, jobject, jlong bitmapHandle, jintArray pixelArray, jint offset, jint stride, jint x, jint y, jint width, jint height) { - const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); + SkBitmap bitmap; + reinterpret_cast<Bitmap*>(bitmapHandle)->getSkBitmap(&bitmap); GraphicsJNI::SetPixels(env, pixelArray, offset, stride, - x, y, width, height, *bitmap); + x, y, width, height, bitmap); } static void Bitmap_copyPixelsToBuffer(JNIEnv* env, jobject, jlong bitmapHandle, jobject jbuffer) { - const SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); - SkAutoLockPixels alp(*bitmap); - const void* src = bitmap->getPixels(); + SkBitmap bitmap; + reinterpret_cast<Bitmap*>(bitmapHandle)->getSkBitmap(&bitmap); + SkAutoLockPixels alp(bitmap); + const void* src = bitmap.getPixels(); if (NULL != src) { android::AutoBufferPointer abp(env, jbuffer, JNI_TRUE); // the java side has already checked that buffer is large enough - memcpy(abp.pointer(), src, bitmap->getSize()); + memcpy(abp.pointer(), src, bitmap.getSize()); } } static void Bitmap_copyPixelsFromBuffer(JNIEnv* env, jobject, jlong bitmapHandle, jobject jbuffer) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); - SkAutoLockPixels alp(*bitmap); - void* dst = bitmap->getPixels(); + SkBitmap bitmap; + reinterpret_cast<Bitmap*>(bitmapHandle)->getSkBitmap(&bitmap); + SkAutoLockPixels alp(bitmap); + void* dst = bitmap.getPixels(); if (NULL != dst) { android::AutoBufferPointer abp(env, jbuffer, JNI_FALSE); // the java side has already checked that buffer is large enough - memcpy(dst, abp.pointer(), bitmap->getSize()); - bitmap->notifyPixelsChanged(); + memcpy(dst, abp.pointer(), bitmap.getSize()); + bitmap.notifyPixelsChanged(); } } static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle, jlong bm1Handle) { - const SkBitmap* bm0 = reinterpret_cast<SkBitmap*>(bm0Handle); - const SkBitmap* bm1 = reinterpret_cast<SkBitmap*>(bm1Handle); - if (bm0->width() != bm1->width() || - bm0->height() != bm1->height() || - bm0->colorType() != bm1->colorType()) { + SkBitmap bm0; + SkBitmap bm1; + reinterpret_cast<Bitmap*>(bm0Handle)->getSkBitmap(&bm0); + reinterpret_cast<Bitmap*>(bm1Handle)->getSkBitmap(&bm1); + if (bm0.width() != bm1.width() || + bm0.height() != bm1.height() || + bm0.colorType() != bm1.colorType()) { return JNI_FALSE; } - SkAutoLockPixels alp0(*bm0); - SkAutoLockPixels alp1(*bm1); + SkAutoLockPixels alp0(bm0); + SkAutoLockPixels alp1(bm1); // if we can't load the pixels, return false - if (NULL == bm0->getPixels() || NULL == bm1->getPixels()) { + if (NULL == bm0.getPixels() || NULL == bm1.getPixels()) { return JNI_FALSE; } - if (bm0->colorType() == kIndex_8_SkColorType) { - SkColorTable* ct0 = bm0->getColorTable(); - SkColorTable* ct1 = bm1->getColorTable(); + if (bm0.colorType() == kIndex_8_SkColorType) { + SkColorTable* ct0 = bm0.getColorTable(); + SkColorTable* ct1 = bm1.getColorTable(); if (NULL == ct0 || NULL == ct1) { return JNI_FALSE; } @@ -829,16 +1157,16 @@ static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle, // now compare each scanline. We can't do the entire buffer at once, // since we don't care about the pixel values that might extend beyond // the width (since the scanline might be larger than the logical width) - const int h = bm0->height(); - const size_t size = bm0->width() * bm0->bytesPerPixel(); + const int h = bm0.height(); + const size_t size = bm0.width() * bm0.bytesPerPixel(); for (int y = 0; y < h; y++) { // SkBitmap::getAddr(int, int) may return NULL due to unrecognized config // (ex: kRLE_Index8_Config). This will cause memcmp method to crash. Since bm0 // and bm1 both have pixel data() (have passed NULL == getPixels() check), // those 2 bitmaps should be valid (only unrecognized), we return JNI_FALSE // to warn user those 2 unrecognized config bitmaps may be different. - void *bm0Addr = bm0->getAddr(0, y); - void *bm1Addr = bm1->getAddr(0, y); + void *bm0Addr = bm0.getAddr(0, y); + void *bm1Addr = bm1.getAddr(0, y); if(bm0Addr == NULL || bm1Addr == NULL) { return JNI_FALSE; @@ -851,15 +1179,9 @@ static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle, return JNI_TRUE; } -static void Bitmap_prepareToDraw(JNIEnv* env, jobject, jlong bitmapHandle) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); - bitmap->lockPixels(); - bitmap->unlockPixels(); -} - static jlong Bitmap_refPixelRef(JNIEnv* env, jobject, jlong bitmapHandle) { - SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle); - SkPixelRef* pixelRef = bitmap ? bitmap->pixelRef() : nullptr; + LocalScopedBitmap bitmap(bitmapHandle); + SkPixelRef* pixelRef = bitmap.valid() ? bitmap->peekAtPixelRef() : nullptr; SkSafeRef(pixelRef); return reinterpret_cast<jlong>(pixelRef); } @@ -902,7 +1224,6 @@ static JNINativeMethod gBitmapMethods[] = { { "nativeCopyPixelsFromBuffer", "(JLjava/nio/Buffer;)V", (void*)Bitmap_copyPixelsFromBuffer }, { "nativeSameAs", "(JJ)Z", (void*)Bitmap_sameAs }, - { "nativePrepareToDraw", "(J)V", (void*)Bitmap_prepareToDraw }, { "nativeRefPixelRef", "(J)J", (void*)Bitmap_refPixelRef }, }; diff --git a/core/jni/android/graphics/Bitmap.h b/core/jni/android/graphics/Bitmap.h new file mode 100644 index 0000000..efeb898 --- /dev/null +++ b/core/jni/android/graphics/Bitmap.h @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2015 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. + */ +#ifndef BITMAP_H_ +#define BITMAP_H_ + +#include <jni.h> +#include <SkBitmap.h> +#include <SkColorTable.h> +#include <SkImageInfo.h> +#include <utils/Mutex.h> +#include <memory> + +namespace android { + +enum class PixelStorageType { + Invalid, + External, + Java, +}; + +class WrappedPixelRef; + +typedef void (*FreeFunc)(void* addr, void* context); + +/** + * Glue-thingy that deals with managing the interaction between the Java + * Bitmap object & SkBitmap along with trying to map a notion of strong/weak + * lifecycles onto SkPixelRef which only has strong counts to avoid requiring + * two GC passes to free the byte[] that backs a Bitmap. + * + * Since not all Bitmaps are byte[]-backed it also supports external allocations, + * which currently is used by screenshots to wrap a gralloc buffer. + */ +class Bitmap { +public: + Bitmap(JNIEnv* env, jbyteArray storageObj, void* address, + const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable); + Bitmap(void* address, void* context, FreeFunc freeFunc, + const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable); + + const SkImageInfo& info() const; + + // Returns nullptr if it is not backed by a jbyteArray + jbyteArray javaByteArray() const { + return mPixelStorageType == PixelStorageType::Java + ? mPixelStorage.java.jstrongRef : nullptr; + } + + int width() const { return info().width(); } + int height() const { return info().height(); } + size_t rowBytes() const; + SkPixelRef* peekAtPixelRef() const; + SkPixelRef* refPixelRef(); + bool valid() const { return mPixelStorageType != PixelStorageType::Invalid; } + + void reconfigure(const SkImageInfo& info, size_t rowBytes, SkColorTable* ctable); + void reconfigure(const SkImageInfo& info); + + void getSkBitmap(SkBitmap* outBitmap); + void detachFromJava(); + + void freePixels(); + + bool hasHardwareMipMap(); + void setHasHardwareMipMap(bool hasMipMap); + +private: + friend class WrappedPixelRef; + + ~Bitmap(); + void doFreePixels(); + void onStrongRefDestroyed(); + + void pinPixelsLocked(); + void unpinPixelsLocked(); + JNIEnv* jniEnv(); + bool shouldDisposeSelfLocked(); + void assertValid() const; + SkPixelRef* refPixelRefLocked(); + + android::Mutex mLock; + int mPinnedRefCount = 0; + std::unique_ptr<WrappedPixelRef> mPixelRef; + PixelStorageType mPixelStorageType; + bool mAttachedToJava = true; + + union { + struct { + void* address; + void* context; + FreeFunc freeFunc; + } external; + struct { + JavaVM* jvm; + jweak jweakRef; + jbyteArray jstrongRef; + } java; + } mPixelStorage; +}; + +} // namespace android + +#endif /* BITMAP_H_ */ diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index d4069a1..3ca4e72 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -156,13 +156,11 @@ private: class RecyclingPixelAllocator : public SkBitmap::Allocator { public: - RecyclingPixelAllocator(SkPixelRef* pixelRef, unsigned int size) - : mPixelRef(pixelRef), mSize(size) { - SkSafeRef(mPixelRef); + RecyclingPixelAllocator(android::Bitmap* bitmap, unsigned int size) + : mBitmap(bitmap), mSize(size) { } ~RecyclingPixelAllocator() { - SkSafeUnref(mPixelRef); } virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { @@ -185,11 +183,9 @@ public: return false; } - // Create a new pixelref with the new ctable that wraps the previous pixelref - SkPixelRef* pr = new AndroidPixelRef(*static_cast<AndroidPixelRef*>(mPixelRef), - info, bitmap->rowBytes(), ctable); + mBitmap->reconfigure(info, bitmap->rowBytes(), ctable); + bitmap->setPixelRef(mBitmap->refPixelRef())->unref(); - bitmap->setPixelRef(pr)->unref(); // since we're already allocated, we lockPixels right away // HeapAllocator/JavaPixelAllocator behaves this way too bitmap->lockPixels(); @@ -197,7 +193,7 @@ public: } private: - SkPixelRef* const mPixelRef; + android::Bitmap* const mBitmap; const unsigned int mSize; }; @@ -258,27 +254,24 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding decoder->setPreferQualityOverSpeed(preferQualityOverSpeed); decoder->setRequireUnpremultipliedColors(requireUnpremultiplied); - SkBitmap* outputBitmap = NULL; + android::Bitmap* reuseBitmap = nullptr; unsigned int existingBufferSize = 0; if (javaBitmap != NULL) { - outputBitmap = GraphicsJNI::getSkBitmapDeprecated(env, javaBitmap); - if (outputBitmap->isImmutable()) { + reuseBitmap = GraphicsJNI::getBitmap(env, javaBitmap); + if (reuseBitmap->peekAtPixelRef()->isImmutable()) { ALOGW("Unable to reuse an immutable bitmap as an image decoder target."); javaBitmap = NULL; - outputBitmap = NULL; + reuseBitmap = nullptr; } else { existingBufferSize = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap); } } - SkAutoTDelete<SkBitmap> adb(outputBitmap == NULL ? new SkBitmap : NULL); - if (outputBitmap == NULL) outputBitmap = adb.get(); - NinePatchPeeker peeker(decoder); decoder->setPeeker(&peeker); JavaPixelAllocator javaAllocator(env); - RecyclingPixelAllocator recyclingAllocator(outputBitmap->pixelRef(), existingBufferSize); + RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize); ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize); SkBitmap::Allocator* outputAllocator = (javaBitmap != NULL) ? (SkBitmap::Allocator*)&recyclingAllocator : (SkBitmap::Allocator*)&javaAllocator; @@ -374,6 +367,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding } } + SkBitmap outputBitmap; if (willScale) { // This is weird so let me explain: we could use the scale parameter // directly, but for historical reasons this is how the corresponding @@ -388,26 +382,27 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding // FIXME: If the alphaType is kUnpremul and the image has alpha, the // colors may not be correct, since Skia does not yet support drawing // to/from unpremultiplied bitmaps. - outputBitmap->setInfo(SkImageInfo::Make(scaledWidth, scaledHeight, + outputBitmap.setInfo(SkImageInfo::Make(scaledWidth, scaledHeight, colorType, decodingBitmap.alphaType())); - if (!outputBitmap->tryAllocPixels(outputAllocator, NULL)) { + if (!outputBitmap.tryAllocPixels(outputAllocator, NULL)) { return nullObjectReturn("allocation failed for scaled bitmap"); } // If outputBitmap's pixels are newly allocated by Java, there is no need // to erase to 0, since the pixels were initialized to 0. if (outputAllocator != &javaAllocator) { - outputBitmap->eraseColor(0); + outputBitmap.eraseColor(0); } SkPaint paint; paint.setFilterQuality(kLow_SkFilterQuality); - SkCanvas canvas(*outputBitmap); + SkCanvas canvas(outputBitmap); canvas.scale(sx, sy); + canvas.drawARGB(0x00, 0x00, 0x00, 0x00); canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint); } else { - outputBitmap->swap(decodingBitmap); + outputBitmap.swap(decodingBitmap); } if (padding) { @@ -422,22 +417,19 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding // if we get here, we're in kDecodePixels_Mode and will therefore // already have a pixelref installed. - if (outputBitmap->pixelRef() == NULL) { + if (outputBitmap.pixelRef() == NULL) { return nullObjectReturn("Got null SkPixelRef"); } if (!isMutable && javaBitmap == NULL) { // promise we will never change our pixels (great for sharing and pictures) - outputBitmap->setImmutable(); + outputBitmap.setImmutable(); } - // detach bitmap from its autodeleter, since we want to own it now - adb.detach(); - if (javaBitmap != NULL) { bool isPremultiplied = !requireUnpremultiplied; - GraphicsJNI::reinitBitmap(env, javaBitmap, outputBitmap, isPremultiplied); - outputBitmap->notifyPixelsChanged(); + GraphicsJNI::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied); + outputBitmap.notifyPixelsChanged(); // If a java bitmap was passed in for reuse, pass it back return javaBitmap; } @@ -447,7 +439,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding if (!requireUnpremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied; // now create the java bitmap - return GraphicsJNI::createBitmap(env, outputBitmap, javaAllocator.getStorageObj(), + return GraphicsJNI::createBitmap(env, javaAllocator.getStorageObjAndReset(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1); } diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp index aeea808..08a3f6f 100644 --- a/core/jni/android/graphics/BitmapRegionDecoder.cpp +++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp @@ -212,26 +212,21 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, region.fTop = start_y; region.fRight = start_x + width; region.fBottom = start_y + height; - SkBitmap* bitmap = NULL; - SkAutoTDelete<SkBitmap> adb; + SkBitmap bitmap; if (tileBitmap != NULL) { // Re-use bitmap. - bitmap = GraphicsJNI::getSkBitmapDeprecated(env, tileBitmap); - } - if (bitmap == NULL) { - bitmap = new SkBitmap; - adb.reset(bitmap); + GraphicsJNI::getSkBitmap(env, tileBitmap, &bitmap); } - if (!brd->decodeRegion(bitmap, region, prefColorType, sampleSize)) { + if (!brd->decodeRegion(&bitmap, region, prefColorType, sampleSize)) { return nullObjectReturn("decoder->decodeRegion returned false"); } // update options (if any) if (NULL != options) { - env->SetIntField(options, gOptions_widthFieldID, bitmap->width()); - env->SetIntField(options, gOptions_heightFieldID, bitmap->height()); + env->SetIntField(options, gOptions_widthFieldID, bitmap.width()); + env->SetIntField(options, gOptions_heightFieldID, bitmap.height()); // TODO: set the mimeType field with the data from the codec. // but how to reuse a set of strings, rather than allocating new one // each time? @@ -240,19 +235,16 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, } if (tileBitmap != NULL) { - bitmap->notifyPixelsChanged(); + bitmap.notifyPixelsChanged(); return tileBitmap; } - // detach bitmap from its autodeleter, since we want to own it now - adb.detach(); - JavaPixelAllocator* allocator = (JavaPixelAllocator*) decoder->getAllocator(); - jbyteArray buff = allocator->getStorageObjAndReset(); int bitmapCreateFlags = 0; if (!requireUnpremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied; - return GraphicsJNI::createBitmap(env, bitmap, buff, bitmapCreateFlags, NULL, NULL, -1); + return GraphicsJNI::createBitmap(env, allocator->getStorageObjAndReset(), + bitmapCreateFlags); } static jint nativeGetHeight(JNIEnv* env, jobject, jlong brdHandle) { diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index f793df1..1c6f7de 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -154,7 +154,7 @@ static jfieldID gPointF_xFieldID; static jfieldID gPointF_yFieldID; static jclass gBitmap_class; -static jfieldID gBitmap_skBitmapPtr; +static jfieldID gBitmap_nativePtr; static jmethodID gBitmap_constructorMethodID; static jmethodID gBitmap_reinitMethodID; static jmethodID gBitmap_getAllocationByteCountMethodID; @@ -338,27 +338,22 @@ SkColorType GraphicsJNI::legacyBitmapConfigToColorType(jint legacyConfig) { return static_cast<SkColorType>(gConfig2ColorType[legacyConfig]); } -SkBitmap* GraphicsJNI::getSkBitmapDeprecated(JNIEnv* env, jobject bitmap) { +android::Bitmap* GraphicsJNI::getBitmap(JNIEnv* env, jobject bitmap) { SkASSERT(env); SkASSERT(bitmap); SkASSERT(env->IsInstanceOf(bitmap, gBitmap_class)); - jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_skBitmapPtr); - SkBitmap* b = reinterpret_cast<SkBitmap*>(bitmapHandle); + jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_nativePtr); + android::Bitmap* b = reinterpret_cast<android::Bitmap*>(bitmapHandle); SkASSERT(b); return b; } void GraphicsJNI::getSkBitmap(JNIEnv* env, jobject bitmap, SkBitmap* outBitmap) { - // TODO: We have to copy from the existing bitmap due to rowBytes not - // being updated on the SkPixelRef at reconfigure time. This is a short term - // problem that will be fixed with the specialized wrapper - *outBitmap = *getSkBitmapDeprecated(env, bitmap); + getBitmap(env, bitmap)->getSkBitmap(outBitmap); } -SkPixelRef* GraphicsJNI::getSkPixelRef(JNIEnv* env, jobject bitmap) { - jlong bitmapHandle = env->GetLongField(bitmap, gBitmap_skBitmapPtr); - SkBitmap* b = reinterpret_cast<SkBitmap*>(bitmapHandle); - return b->pixelRef(); +SkPixelRef* GraphicsJNI::refSkPixelRef(JNIEnv* env, jobject bitmap) { + return getBitmap(env, bitmap)->refPixelRef(); } SkColorType GraphicsJNI::getNativeBitmapColorType(JNIEnv* env, jobject jconfig) { @@ -396,47 +391,43 @@ SkRegion* GraphicsJNI::getNativeRegion(JNIEnv* env, jobject region) /////////////////////////////////////////////////////////////////////////////////////////// // Assert that bitmap's SkAlphaType is consistent with isPremultiplied. -static void assert_premultiplied(const SkBitmap& bitmap, bool isPremultiplied) { +static void assert_premultiplied(const SkImageInfo& info, bool isPremultiplied) { // kOpaque_SkAlphaType and kIgnore_SkAlphaType mean that isPremultiplied is // irrelevant. This just tests to ensure that the SkAlphaType is not // opposite of isPremultiplied. if (isPremultiplied) { - SkASSERT(bitmap.alphaType() != kUnpremul_SkAlphaType); + SkASSERT(info.alphaType() != kUnpremul_SkAlphaType); } else { - SkASSERT(bitmap.alphaType() != kPremul_SkAlphaType); + SkASSERT(info.alphaType() != kPremul_SkAlphaType); } } -jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer, - int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets, int density) -{ - SkASSERT(bitmap); - SkASSERT(bitmap->pixelRef()); - SkASSERT(!env->ExceptionCheck()); +jobject GraphicsJNI::createBitmap(JNIEnv* env, android::Bitmap* bitmap, + int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets, + int density) { bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable; bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied; - // The caller needs to have already set the alpha type properly, so the // native SkBitmap stays in sync with the Java Bitmap. - assert_premultiplied(*bitmap, isPremultiplied); + assert_premultiplied(bitmap->info(), isPremultiplied); jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID, - reinterpret_cast<jlong>(bitmap), buffer, + reinterpret_cast<jlong>(bitmap), bitmap->javaByteArray(), bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied, ninePatchChunk, ninePatchInsets); hasException(env); // For the side effect of logging. return obj; } -void GraphicsJNI::reinitBitmap(JNIEnv* env, jobject javaBitmap, SkBitmap* bitmap, +void GraphicsJNI::reinitBitmap(JNIEnv* env, jobject javaBitmap, const SkImageInfo& info, bool isPremultiplied) { // The caller needs to have already set the alpha type properly, so the // native SkBitmap stays in sync with the Java Bitmap. - assert_premultiplied(*bitmap, isPremultiplied); + assert_premultiplied(info, isPremultiplied); env->CallVoidMethod(javaBitmap, gBitmap_reinitMethodID, - bitmap->width(), bitmap->height(), isPremultiplied); + info.width(), info.height(), isPremultiplied); } int GraphicsJNI::getBitmapAllocationByteCount(JNIEnv* env, jobject javaBitmap) @@ -477,51 +468,6 @@ static JNIEnv* vm2env(JavaVM* vm) /////////////////////////////////////////////////////////////////////////////// -AndroidPixelRef::AndroidPixelRef(JNIEnv* env, const SkImageInfo& info, void* storage, - size_t rowBytes, jbyteArray storageObj, SkColorTable* ctable) : - SkMallocPixelRef(info, storage, rowBytes, ctable, (storageObj == NULL)), - fWrappedPixelRef(NULL) { - SkASSERT(storage); - SkASSERT(storageObj); - SkASSERT(env); - - if (env->GetJavaVM(&fVM) != JNI_OK) { - SkDebugf("------ [%p] env->GetJavaVM failed\n", env); - sk_throw(); - } - - fStorageObj = (jbyteArray) env->NewGlobalRef(storageObj); -} - -AndroidPixelRef::AndroidPixelRef(AndroidPixelRef& wrappedPixelRef, const SkImageInfo& info, - size_t rowBytes, SkColorTable* ctable) : - SkMallocPixelRef(info, wrappedPixelRef.getAddr(), rowBytes, ctable, false), - fWrappedPixelRef(wrappedPixelRef.fWrappedPixelRef ? - wrappedPixelRef.fWrappedPixelRef : &wrappedPixelRef) -{ - SkASSERT(fWrappedPixelRef); - SkSafeRef(fWrappedPixelRef); - - // don't need to initialize this, as all the relevant logic delegates to the wrapped ref - fStorageObj = NULL; -} - -AndroidPixelRef::~AndroidPixelRef() { - if (fWrappedPixelRef) { - SkSafeUnref(fWrappedPixelRef); - } else { - SkASSERT(fStorageObj); - JNIEnv* env = vm2env(fVM); - env->DeleteGlobalRef(fStorageObj); - } - - if (android::uirenderer::Caches::hasInstance()) { - android::uirenderer::Caches::getInstance().textureCache.releaseTexture(getStableID()); - } -} - -/////////////////////////////////////////////////////////////////////////////// - static bool computeAllocationSize(const SkBitmap& bitmap, size_t* size) { int32_t rowBytes32 = SkToS32(bitmap.rowBytes()); int64_t bigSize = (int64_t)bitmap.height() * rowBytes32; @@ -533,7 +479,7 @@ static bool computeAllocationSize(const SkBitmap& bitmap, size_t* size) { return true; } -jbyteArray GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap, +android::Bitmap* GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap, SkColorTable* ctable) { const SkImageInfo& info = bitmap->info(); if (info.fColorType == kUnknown_SkColorType) { @@ -562,13 +508,14 @@ jbyteArray GraphicsJNI::allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap, return NULL; } SkASSERT(addr); - SkPixelRef* pr = new AndroidPixelRef(env, info, (void*) addr, rowBytes, arrayObj, ctable); - bitmap->setPixelRef(pr)->unref(); + android::Bitmap* wrapper = new android::Bitmap(env, arrayObj, (void*) addr, + info, rowBytes, ctable); + wrapper->getSkBitmap(bitmap); // since we're already allocated, we lockPixels right away // HeapAllocator behaves this way too bitmap->lockPixels(); - return arrayObj; + return wrapper; } struct AndroidPixelRefContext { @@ -627,21 +574,22 @@ bool GraphicsJNI::allocatePixels(JNIEnv* env, SkBitmap* bitmap, SkColorTable* ct /////////////////////////////////////////////////////////////////////////////// -JavaPixelAllocator::JavaPixelAllocator(JNIEnv* env) - : fStorageObj(NULL), - fAllocCount(0) { - if (env->GetJavaVM(&fVM) != JNI_OK) { - SkDebugf("------ [%p] env->GetJavaVM failed\n", env); - sk_throw(); +JavaPixelAllocator::JavaPixelAllocator(JNIEnv* env) { + LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&mJavaVM) != JNI_OK, + "env->GetJavaVM failed"); +} + +JavaPixelAllocator::~JavaPixelAllocator() { + if (mStorage) { + mStorage->detachFromJava(); } } bool JavaPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { - JNIEnv* env = vm2env(fVM); + JNIEnv* env = vm2env(mJavaVM); - fStorageObj = GraphicsJNI::allocateJavaPixelRef(env, bitmap, ctable); - fAllocCount += 1; - return fStorageObj != NULL; + mStorage = GraphicsJNI::allocateJavaPixelRef(env, bitmap, ctable); + return mStorage != nullptr; } //////////////////////////////////////////////////////////////////////////////// @@ -687,7 +635,7 @@ int register_android_graphics_Graphics(JNIEnv* env) gPointF_yFieldID = getFieldIDCheck(env, gPointF_class, "y", "F"); gBitmap_class = make_globalref(env, "android/graphics/Bitmap"); - gBitmap_skBitmapPtr = getFieldIDCheck(env, gBitmap_class, "mSkBitmapPtr", "J"); + gBitmap_nativePtr = getFieldIDCheck(env, gBitmap_class, "mNativePtr", "J"); gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>", "(J[BIIIZZ[BLandroid/graphics/NinePatch$InsetStruct;)V"); gBitmap_reinitMethodID = env->GetMethodID(gBitmap_class, "reinit", "(IIZ)V"); gBitmap_getAllocationByteCountMethodID = env->GetMethodID(gBitmap_class, "getAllocationByteCount", "()I"); diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index 8eb43f8..ef9c2a9 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -1,6 +1,7 @@ #ifndef _ANDROID_GRAPHICS_GRAPHICS_JNI_H_ #define _ANDROID_GRAPHICS_GRAPHICS_JNI_H_ +#include "Bitmap.h" #include "SkBitmap.h" #include "SkDevice.h" #include "SkPixelRef.h" @@ -49,9 +50,9 @@ public: static void point_to_jpointf(const SkPoint& point, JNIEnv*, jobject jpointf); static android::Canvas* getNativeCanvas(JNIEnv*, jobject canvas); - static SkBitmap* getSkBitmapDeprecated(JNIEnv*, jobject bitmap); + static android::Bitmap* getBitmap(JNIEnv*, jobject bitmap); static void getSkBitmap(JNIEnv*, jobject bitmap, SkBitmap* outBitmap); - static SkPixelRef* getSkPixelRef(JNIEnv*, jobject bitmap); + static SkPixelRef* refSkPixelRef(JNIEnv*, jobject bitmap); static SkRegion* getNativeRegion(JNIEnv*, jobject region); // Given the 'native' long held by the Rasterizer.java object, return a @@ -71,22 +72,18 @@ public: */ static SkColorType getNativeBitmapColorType(JNIEnv*, jobject jconfig); - /** Create a java Bitmap object given the native bitmap (required) and optional - storage array (may be null). - bitmap's SkAlphaType must already be in sync with bitmapCreateFlags. + /* + * Create a java Bitmap object given the native bitmap + * bitmap's SkAlphaType must already be in sync with bitmapCreateFlags. */ - static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer, - int bitmapCreateFlags, jbyteArray ninePatch, jobject ninePatchInsets, int density = -1); - - static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, int bitmapCreateFlags, - jbyteArray ninePatch, int density = -1) { - return createBitmap(env, bitmap, NULL, bitmapCreateFlags, ninePatch, NULL, density); - } + static jobject createBitmap(JNIEnv* env, android::Bitmap* bitmap, + int bitmapCreateFlags, jbyteArray ninePatchChunk = NULL, + jobject ninePatchInsets = NULL, int density = -1); /** Reinitialize a bitmap. bitmap must already have its SkAlphaType set in sync with isPremultiplied */ - static void reinitBitmap(JNIEnv* env, jobject javaBitmap, SkBitmap* bitmap, + static void reinitBitmap(JNIEnv* env, jobject javaBitmap, const SkImageInfo& info, bool isPremultiplied); static int getBitmapAllocationByteCount(JNIEnv* env, jobject javaBitmap); @@ -95,7 +92,7 @@ public: static jobject createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap); - static jbyteArray allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap, + static android::Bitmap* allocateJavaPixelRef(JNIEnv* env, SkBitmap* bitmap, SkColorTable* ctable); /** @@ -113,30 +110,6 @@ public: static bool SetPixels(JNIEnv* env, jintArray colors, int srcOffset, int srcStride, int x, int y, int width, int height, const SkBitmap& dstBitmap); - - static jbyteArray getBitmapStorageObj(SkPixelRef *pixref); -}; - -class AndroidPixelRef : public SkMallocPixelRef { -public: - AndroidPixelRef(JNIEnv* env, const SkImageInfo& info, void* storage, size_t rowBytes, - jbyteArray storageObj, SkColorTable* ctable); - - /** - * Creates an AndroidPixelRef that wraps (and refs) another to reuse/share - * the same storage and java byte array refcounting, yet have a different - * color table. - */ - AndroidPixelRef(AndroidPixelRef& wrappedPixelRef, const SkImageInfo& info, - size_t rowBytes, SkColorTable* ctable); - - virtual ~AndroidPixelRef(); - -private: - AndroidPixelRef* const fWrappedPixelRef; // if set, delegate memory management calls to this - - JavaVM* fVM; - jbyteArray fStorageObj; // The Java byte[] object used as the bitmap backing store }; /** Allocator which allocates the backing buffer in the Java heap. @@ -147,30 +120,22 @@ private: class JavaPixelAllocator : public SkBitmap::Allocator { public: JavaPixelAllocator(JNIEnv* env); - // overrides - virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable); + ~JavaPixelAllocator(); - /** Return the Java array object created for the last allocation. - * This returns a local JNI reference which the caller is responsible - * for storing appropriately (usually by passing it to the Bitmap - * constructor). - */ - jbyteArray getStorageObj() { return fStorageObj; } + virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) override; - /** Same as getStorageObj(), but also resets the allocator so that it - * can allocate again. + /** + * Fetches the backing allocation object. Must be called! */ - jbyteArray getStorageObjAndReset() { - jbyteArray result = fStorageObj; - fStorageObj = NULL; - fAllocCount = 0; + android::Bitmap* getStorageObjAndReset() { + android::Bitmap* result = mStorage; + mStorage = NULL; return result; }; private: - JavaVM* fVM; - jbyteArray fStorageObj; - int fAllocCount; + JavaVM* mJavaVM; + android::Bitmap* mStorage = nullptr; }; enum JNIAccess { diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp index 6afb226..87b81d5 100644 --- a/core/jni/android_media_AudioRecord.cpp +++ b/core/jni/android_media_AudioRecord.cpp @@ -30,6 +30,7 @@ #include "android_media_AudioFormat.h" #include "android_media_AudioErrors.h" +#include "android_media_DeviceCallback.h" // ---------------------------------------------------------------------------- @@ -44,6 +45,7 @@ struct audio_record_fields_t { jmethodID postNativeEventInJava; //... event post callback method jfieldID nativeRecorderInJavaObj; // provides access to the C++ AudioRecord object jfieldID nativeCallbackCookie; // provides access to the AudioRecord callback data + jfieldID nativeDeviceCallback; // provides access to the JNIDeviceCallback instance }; struct audio_attributes_fields_t { jfieldID fieldRecSource; // AudioAttributes.mSource @@ -120,6 +122,33 @@ static void recorderCallback(int event, void* user, void *info) { } } +static sp<JNIDeviceCallback> getJniDeviceCallback(JNIEnv* env, jobject thiz) +{ + Mutex::Autolock l(sLock); + JNIDeviceCallback* const cb = + (JNIDeviceCallback*)env->GetLongField(thiz, + javaAudioRecordFields.nativeDeviceCallback); + return sp<JNIDeviceCallback>(cb); +} + +static sp<JNIDeviceCallback> setJniDeviceCallback(JNIEnv* env, + jobject thiz, + const sp<JNIDeviceCallback>& cb) +{ + Mutex::Autolock l(sLock); + sp<JNIDeviceCallback> old = + (JNIDeviceCallback*)env->GetLongField(thiz, + javaAudioRecordFields.nativeDeviceCallback); + if (cb.get()) { + cb->incStrong((void*)setJniDeviceCallback); + } + if (old != 0) { + old->decStrong((void*)setJniDeviceCallback); + } + env->SetLongField(thiz, javaAudioRecordFields.nativeDeviceCallback, (jlong)cb.get()); + return old; +} + // ---------------------------------------------------------------------------- static sp<AudioRecord> getAudioRecord(JNIEnv* env, jobject thiz) { @@ -252,6 +281,7 @@ android_media_AudioRecord_setup(JNIEnv *env, jobject thiz, jobject weak_this, sessionId, AudioRecord::TRANSFER_DEFAULT, flags, + -1, -1, // default uid, pid paa); if (status != NO_ERROR) { @@ -592,9 +622,63 @@ static jboolean android_media_AudioRecord_setInputDevice( JNIEnv *env, jobject thiz, jint device_id) { sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz); + if (lpRecorder == 0) { + return 0; + } return lpRecorder->setInputDevice(device_id) == NO_ERROR; } +static jint android_media_AudioRecord_getRoutedDeviceId( + JNIEnv *env, jobject thiz) { + + sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz); + if (lpRecorder == 0) { + return 0; + } + return (jint)lpRecorder->getRoutedDeviceId(); +} + +static void android_media_AudioRecord_enableDeviceCallback( + JNIEnv *env, jobject thiz) { + + sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz); + if (lpRecorder == 0) { + return; + } + sp<JNIDeviceCallback> cb = getJniDeviceCallback(env, thiz); + if (cb != 0) { + return; + } + audiorecord_callback_cookie *cookie = + (audiorecord_callback_cookie *)env->GetLongField(thiz, + javaAudioRecordFields.nativeCallbackCookie); + if (cookie == NULL) { + return; + } + + cb = new JNIDeviceCallback(env, thiz, cookie->audioRecord_ref, + javaAudioRecordFields.postNativeEventInJava); + status_t status = lpRecorder->addAudioDeviceCallback(cb); + if (status == NO_ERROR) { + setJniDeviceCallback(env, thiz, cb); + } +} + +static void android_media_AudioRecord_disableDeviceCallback( + JNIEnv *env, jobject thiz) { + + sp<AudioRecord> lpRecorder = getAudioRecord(env, thiz); + if (lpRecorder == 0) { + return; + } + sp<JNIDeviceCallback> cb = setJniDeviceCallback(env, thiz, 0); + if (cb != 0) { + lpRecorder->removeAudioDeviceCallback(cb); + } +} + + + // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- static JNINativeMethod gMethods[] = { @@ -627,12 +711,17 @@ static JNINativeMethod gMethods[] = { {"native_get_min_buff_size", "(III)I", (void *)android_media_AudioRecord_get_min_buff_size}, {"native_setInputDevice", "(I)Z", (void *)android_media_AudioRecord_setInputDevice}, + {"native_getRoutedDeviceId", "()I", (void *)android_media_AudioRecord_getRoutedDeviceId}, + {"native_enableDeviceCallback", "()V", (void *)android_media_AudioRecord_enableDeviceCallback}, + {"native_disableDeviceCallback", "()V", + (void *)android_media_AudioRecord_disableDeviceCallback}, }; // field names found in android/media/AudioRecord.java #define JAVA_POSTEVENT_CALLBACK_NAME "postEventFromNative" #define JAVA_NATIVERECORDERINJAVAOBJ_FIELD_NAME "mNativeRecorderInJavaObj" #define JAVA_NATIVECALLBACKINFO_FIELD_NAME "mNativeCallbackCookie" +#define JAVA_NATIVEDEVICECALLBACK_FIELD_NAME "mNativeDeviceCallback" // ---------------------------------------------------------------------------- int register_android_media_AudioRecord(JNIEnv *env) @@ -640,6 +729,7 @@ int register_android_media_AudioRecord(JNIEnv *env) javaAudioRecordFields.postNativeEventInJava = NULL; javaAudioRecordFields.nativeRecorderInJavaObj = NULL; javaAudioRecordFields.nativeCallbackCookie = NULL; + javaAudioRecordFields.nativeDeviceCallback = NULL; // Get the AudioRecord class @@ -657,6 +747,9 @@ int register_android_media_AudioRecord(JNIEnv *env) javaAudioRecordFields.nativeCallbackCookie = GetFieldIDOrDie(env, audioRecordClass, JAVA_NATIVECALLBACKINFO_FIELD_NAME, "J"); + javaAudioRecordFields.nativeDeviceCallback = GetFieldIDOrDie(env, + audioRecordClass, JAVA_NATIVEDEVICECALLBACK_FIELD_NAME, "J"); + // Get the AudioAttributes class and fields jclass audioAttrClass = FindClassOrDie(env, kAudioAttributesClassPathName); javaAudioAttrFields.fieldRecSource = GetFieldIDOrDie(env, audioAttrClass, "mSource", "I"); diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 3655adc..eab5668 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -937,7 +937,8 @@ static jint convertAudioPortFromNative(JNIEnv *env, } else if (nAudioPort->type == AUDIO_PORT_TYPE_MIX) { ALOGV("convertAudioPortFromNative is a mix"); *jAudioPort = env->NewObject(gAudioMixPortClass, gAudioMixPortCstor, - jHandle, nAudioPort->role, jDeviceName, + jHandle, nAudioPort->ext.mix.handle, + nAudioPort->role, jDeviceName, jSamplingRates, jChannelMasks, jFormats, jGains); } else { @@ -1670,7 +1671,7 @@ int register_android_media_AudioSystem(JNIEnv *env) jclass audioMixPortClass = FindClassOrDie(env, "android/media/AudioMixPort"); gAudioMixPortClass = MakeGlobalRefOrDie(env, audioMixPortClass); gAudioMixPortCstor = GetMethodIDOrDie(env, audioMixPortClass, "<init>", - "(Landroid/media/AudioHandle;ILjava/lang/String;[I[I[I[Landroid/media/AudioGain;)V"); + "(Landroid/media/AudioHandle;IILjava/lang/String;[I[I[I[Landroid/media/AudioGain;)V"); jclass audioGainClass = FindClassOrDie(env, "android/media/AudioGain"); gAudioGainClass = MakeGlobalRefOrDie(env, audioGainClass); diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 26b82c5..662ecd3 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -36,6 +36,7 @@ #include "android_media_AudioFormat.h" #include "android_media_AudioErrors.h" #include "android_media_PlaybackSettings.h" +#include "android_media_DeviceCallback.h" // ---------------------------------------------------------------------------- @@ -79,6 +80,7 @@ class AudioTrackJniStorage { sp<MemoryHeapBase> mMemHeap; sp<MemoryBase> mMemBase; audiotrack_callback_cookie mCallbackData; + sp<JNIDeviceCallback> mDeviceCallback; AudioTrackJniStorage() { mCallbackData.audioTrack_class = 0; @@ -977,6 +979,51 @@ static jboolean android_media_AudioTrack_setOutputDevice( return lpTrack->setOutputDevice(device_id) == NO_ERROR; } +static jint android_media_AudioTrack_getRoutedDeviceId( + JNIEnv *env, jobject thiz) { + + sp<AudioTrack> lpTrack = getAudioTrack(env, thiz); + if (lpTrack == NULL) { + return 0; + } + return (jint)lpTrack->getRoutedDeviceId(); +} + +static void android_media_AudioTrack_enableDeviceCallback( + JNIEnv *env, jobject thiz) { + + sp<AudioTrack> lpTrack = getAudioTrack(env, thiz); + if (lpTrack == NULL) { + return; + } + AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetLongField( + thiz, javaAudioTrackFields.jniData); + if (pJniStorage == NULL || pJniStorage->mDeviceCallback != 0) { + return; + } + pJniStorage->mDeviceCallback = + new JNIDeviceCallback(env, thiz, pJniStorage->mCallbackData.audioTrack_ref, + javaAudioTrackFields.postNativeEventInJava); + lpTrack->addAudioDeviceCallback(pJniStorage->mDeviceCallback); +} + +static void android_media_AudioTrack_disableDeviceCallback( + JNIEnv *env, jobject thiz) { + + sp<AudioTrack> lpTrack = getAudioTrack(env, thiz); + if (lpTrack == NULL) { + return; + } + AudioTrackJniStorage* pJniStorage = (AudioTrackJniStorage *)env->GetLongField( + thiz, javaAudioTrackFields.jniData); + if (pJniStorage == NULL || pJniStorage->mDeviceCallback == 0) { + return; + } + lpTrack->removeAudioDeviceCallback(pJniStorage->mDeviceCallback); + pJniStorage->mDeviceCallback.clear(); +} + + // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- static JNINativeMethod gMethods[] = { @@ -1030,6 +1077,9 @@ static JNINativeMethod gMethods[] = { "(I)I", (void *)android_media_AudioTrack_attachAuxEffect}, {"native_setOutputDevice", "(I)Z", (void *)android_media_AudioTrack_setOutputDevice}, + {"native_getRoutedDeviceId", "()I", (void *)android_media_AudioTrack_getRoutedDeviceId}, + {"native_enableDeviceCallback", "()V", (void *)android_media_AudioTrack_enableDeviceCallback}, + {"native_disableDeviceCallback", "()V", (void *)android_media_AudioTrack_disableDeviceCallback}, }; diff --git a/core/jni/android_media_DeviceCallback.cpp b/core/jni/android_media_DeviceCallback.cpp new file mode 100644 index 0000000..e159373 --- /dev/null +++ b/core/jni/android_media_DeviceCallback.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 + +#define LOG_TAG "AudioDeviceCallback-JNI" + +#include <utils/Log.h> +#include <JNIHelp.h> +#include <JniConstants.h> +#include "core_jni_helpers.h" +#include <media/AudioSystem.h> + +#include "android_media_DeviceCallback.h" + + +// ---------------------------------------------------------------------------- + +using namespace android; + +JNIDeviceCallback::JNIDeviceCallback(JNIEnv* env, jobject thiz, jobject weak_thiz, + jmethodID postEventFromNative) +{ + + // Hold onto the AudioTrack/AudioRecord class for use in calling the static method + // that posts events to the application thread. + jclass clazz = env->GetObjectClass(thiz); + if (clazz == NULL) { + return; + } + mClass = (jclass)env->NewGlobalRef(clazz); + + // We use a weak reference so the AudioTrack/AudioRecord object can be garbage collected. + // The reference is only used as a proxy for callbacks. + mObject = env->NewGlobalRef(weak_thiz); + + mPostEventFromNative = postEventFromNative; +} + +JNIDeviceCallback::~JNIDeviceCallback() +{ + // remove global references + JNIEnv *env = AndroidRuntime::getJNIEnv(); + if (env == NULL) { + return; + } + env->DeleteGlobalRef(mObject); + env->DeleteGlobalRef(mClass); +} + +void JNIDeviceCallback::onAudioDeviceUpdate(audio_io_handle_t audioIo, + audio_port_handle_t deviceId) +{ + JNIEnv *env = AndroidRuntime::getJNIEnv(); + if (env == NULL) { + return; + } + + ALOGV("%s audioIo %d deviceId %d", __FUNCTION__, audioIo, deviceId); + env->CallStaticVoidMethod(mClass, + mPostEventFromNative, + mObject, + AUDIO_NATIVE_EVENT_ROUTING_CHANGE, deviceId, 0, NULL); + if (env->ExceptionCheck()) { + ALOGW("An exception occurred while notifying an event."); + env->ExceptionClear(); + } +} + diff --git a/core/jni/android_media_DeviceCallback.h b/core/jni/android_media_DeviceCallback.h new file mode 100644 index 0000000..7ae788e --- /dev/null +++ b/core/jni/android_media_DeviceCallback.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2015 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. + */ + +#ifndef ANDROID_MEDIA_DEVICE_CALLBACK_H +#define ANDROID_MEDIA_DEVICE_CALLBACK_H + +#include <system/audio.h> +#include <media/AudioSystem.h> + +namespace android { + +// keep in sync with AudioSystem.java +#define AUDIO_NATIVE_EVENT_ROUTING_CHANGE 1000 + +class JNIDeviceCallback: public AudioSystem::AudioDeviceCallback +{ +public: + JNIDeviceCallback(JNIEnv* env, jobject thiz, jobject weak_thiz, jmethodID postEventFromNative); + ~JNIDeviceCallback(); + + virtual void onAudioDeviceUpdate(audio_io_handle_t audioIo, + audio_port_handle_t deviceId); + +private: + void sendEvent(int event); + + jclass mClass; // Reference to AudioTrack/AudioRecord class + jobject mObject; // Weak ref to AudioTrack/AudioRecord Java object to call on + jmethodID mPostEventFromNative; // postEventFromNative method ID. +}; + +}; // namespace android + +#endif // ANDROID_MEDIA_DEVICE_CALLBACK_H diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index daf5a61..74a9e4e 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -976,6 +976,12 @@ static void android_content_AssetManager_copyTheme(JNIEnv* env, jobject clazz, dest->setTo(*src); } +static void android_content_AssetManager_clearTheme(JNIEnv* env, jobject clazz, jlong themeHandle) +{ + ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); + theme->clear(); +} + static jint android_content_AssetManager_loadThemeAttributeValue( JNIEnv* env, jobject clazz, jlong themeHandle, jint ident, jobject outValue, jboolean resolve) { @@ -999,6 +1005,13 @@ static jint android_content_AssetManager_loadThemeAttributeValue( return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags) : block; } +static jint android_content_AssetManager_getThemeChangingConfigurations(JNIEnv* env, jobject clazz, + jlong themeHandle) +{ + ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle); + return theme->getChangingConfigurations(); +} + static void android_content_AssetManager_dumpTheme(JNIEnv* env, jobject clazz, jlong themeHandle, jint pri, jstring tag, jstring prefix) @@ -2101,8 +2114,12 @@ static JNINativeMethod gAssetManagerMethods[] = { (void*) android_content_AssetManager_applyThemeStyle }, { "copyTheme", "(JJ)V", (void*) android_content_AssetManager_copyTheme }, + { "clearTheme", "(J)V", + (void*) android_content_AssetManager_clearTheme }, { "loadThemeAttributeValue", "(JILandroid/util/TypedValue;Z)I", (void*) android_content_AssetManager_loadThemeAttributeValue }, + { "getThemeChangingConfigurations", "(J)I", + (void*) android_content_AssetManager_getThemeChangingConfigurations }, { "dumpTheme", "(JILjava/lang/String;Ljava/lang/String;)V", (void*) android_content_AssetManager_dumpTheme }, { "applyStyle","(JIIJ[I[I[I)Z", diff --git a/core/jni/android_view_DisplayListCanvas.cpp b/core/jni/android_view_DisplayListCanvas.cpp index 39449b0..bb8ef83 100644 --- a/core/jni/android_view_DisplayListCanvas.cpp +++ b/core/jni/android_view_DisplayListCanvas.cpp @@ -86,24 +86,6 @@ static void android_view_DisplayListCanvas_finish(JNIEnv* env, jobject clazz, renderer->finish(); } -static void android_view_DisplayListCanvas_setProperty(JNIEnv* env, - jobject clazz, jstring name, jstring value) { - if (!Caches::hasInstance()) { - ALOGW("can't set property, no Caches instance"); - return; - } - - if (name == NULL || value == NULL) { - ALOGW("can't set prop, null passed"); - } - - const char* nameCharArray = env->GetStringUTFChars(name, NULL); - const char* valueCharArray = env->GetStringUTFChars(value, NULL); - Caches::getInstance().setTempProperty(nameCharArray, valueCharArray); - env->ReleaseStringUTFChars(name, nameCharArray); - env->ReleaseStringUTFChars(name, valueCharArray); -} - // ---------------------------------------------------------------------------- // Functor // ---------------------------------------------------------------------------- @@ -268,8 +250,6 @@ static JNINativeMethod gMethods[] = { { "nPrepare", "(J)V", (void*) android_view_DisplayListCanvas_prepare }, { "nPrepareDirty", "(JIIII)V", (void*) android_view_DisplayListCanvas_prepareDirty }, { "nFinish", "(J)V", (void*) android_view_DisplayListCanvas_finish }, - { "nSetProperty", "(Ljava/lang/String;Ljava/lang/String;)V", - (void*) android_view_DisplayListCanvas_setProperty }, { "nCallDrawGLFunction", "(JJ)V", (void*) android_view_DisplayListCanvas_callDrawGLFunction }, diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index a8355c2..77af341 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -23,6 +23,7 @@ #include "android_os_Parcel.h" #include "android_util_Binder.h" +#include "android/graphics/Bitmap.h" #include "android/graphics/GraphicsJNI.h" #include "android/graphics/Region.h" @@ -168,22 +169,19 @@ static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, } } - const ssize_t rowBytes = + const size_t rowBytes = screenshot->getStride() * android::bytesPerPixel(screenshot->getFormat()); - SkBitmap* bitmap = new SkBitmap(); - bitmap->setInfo(screenshotInfo, (size_t)rowBytes); - if (screenshotInfo.fWidth > 0 && screenshotInfo.fHeight > 0) { - // takes ownership of ScreenshotClient - SkMallocPixelRef* pixels = SkMallocPixelRef::NewWithProc(screenshotInfo, - (size_t) rowBytes, NULL, (void*) screenshot->getPixels(), &DeleteScreenshot, - (void*) (screenshot.get())); - screenshot.detach(); - pixels->setImmutable(); - bitmap->setPixelRef(pixels)->unref(); - bitmap->lockPixels(); + if (!screenshotInfo.fWidth || !screenshotInfo.fHeight) { + return NULL; } + Bitmap* bitmap = new Bitmap( + (void*) screenshot->getPixels(), (void*) screenshot.get(), DeleteScreenshot, + screenshotInfo, rowBytes, nullptr); + screenshot.detach(); + bitmap->peekAtPixelRef()->setImmutable(); + return GraphicsJNI::createBitmap(env, bitmap, GraphicsJNI::kBitmapCreateFlag_Premultiplied, NULL); } diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp index 4ccbb41..5d5465b 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -388,6 +388,15 @@ static void android_view_ThreadedRenderer_trimMemory(JNIEnv* env, jobject clazz, RenderProxy::trimMemory(level); } +static void android_view_ThreadedRenderer_overrideProperty(JNIEnv* env, jobject clazz, + jstring name, jstring value) { + const char* nameCharArray = env->GetStringUTFChars(name, NULL); + const char* valueCharArray = env->GetStringUTFChars(value, NULL); + RenderProxy::overrideProperty(nameCharArray, valueCharArray); + env->ReleaseStringUTFChars(name, nameCharArray); + env->ReleaseStringUTFChars(name, valueCharArray); +} + static void android_view_ThreadedRenderer_fence(JNIEnv* env, jobject clazz, jlong proxyPtr) { RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); @@ -466,6 +475,7 @@ static JNINativeMethod gMethods[] = { { "nDetachSurfaceTexture", "(JJ)V", (void*) android_view_ThreadedRenderer_detachSurfaceTexture }, { "nDestroyHardwareResources", "(J)V", (void*) android_view_ThreadedRenderer_destroyHardwareResources }, { "nTrimMemory", "(I)V", (void*) android_view_ThreadedRenderer_trimMemory }, + { "nOverrideProperty", "(Ljava/lang/String;Ljava/lang/String;)V", (void*) android_view_ThreadedRenderer_overrideProperty }, { "nFence", "(J)V", (void*) android_view_ThreadedRenderer_fence }, { "nStopDrawing", "(J)V", (void*) android_view_ThreadedRenderer_stopDrawing }, { "nNotifyFramePending", "(J)V", (void*) android_view_ThreadedRenderer_notifyFramePending }, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 942e6a6..5669b91 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2352,11 +2352,6 @@ <permission android:name="android.permission.BIND_CONDITION_PROVIDER_SERVICE" android:protectionLevel="signature" /> - <!-- Must be required by a {@link android.media.routing.MediaRouteService}, - to ensure that only the system can bind to it. --> - <permission android:name="android.permission.BIND_MEDIA_ROUTE_SERVICE" - android:protectionLevel="signature" /> - <!-- Must be required by an {@link android.service.dreams.DreamService}, to ensure that only the system can bind to it. --> <permission android:name="android.permission.BIND_DREAM_SERVICE" @@ -2433,6 +2428,12 @@ <permission android:name="android.permission.QUERY_DO_NOT_ASK_CREDENTIALS_ON_BOOT" android:protectionLevel="signature" /> + <!-- @SystemApi Allows applications to kill UIDs. + <p>Not for use by third-party applications. + @hide --> + <permission android:name="android.permission.KILL_UID" + android:protectionLevel="signature" /> + <!-- The system process is explicitly the only one allowed to launch the confirmation UI for full backup/restore --> <uses-permission android:name="android.permission.CONFIRM_FULL_BACKUP"/> @@ -2445,7 +2446,8 @@ android:backupAgent="com.android.server.backup.SystemBackupAgent" android:killAfterRestore="false" android:icon="@drawable/ic_launcher_android" - android:supportsRtl="true"> + android:supportsRtl="true" + android:theme="@style/Theme.Material.DayNight.DarkActionBar"> <activity android:name="com.android.internal.app.ChooserActivity" android:theme="@style/Theme.DeviceDefault.Resolver" android:finishOnCloseSystemDialogs="true" @@ -2478,7 +2480,7 @@ android:label="@string/managed_profile_label"> </activity-alias> <activity android:name="com.android.internal.app.HeavyWeightSwitcherActivity" - android:theme="@style/Theme.Material.Light.Dialog" + android:theme="@style/Theme.Material.DayNight.Dialog" android:label="@string/heavy_weight_switcher_title" android:finishOnCloseSystemDialogs="true" android:excludeFromRecents="true" @@ -2511,7 +2513,7 @@ <activity android:name="android.accounts.ChooseAccountActivity" android:excludeFromRecents="true" android:exported="true" - android:theme="@style/Theme.Material.Light.Dialog" + android:theme="@style/Theme.Material.DayNight.Dialog" android:label="@string/choose_account_label" android:process=":ui"> </activity> @@ -2519,14 +2521,14 @@ <activity android:name="android.accounts.ChooseTypeAndAccountActivity" android:excludeFromRecents="true" android:exported="true" - android:theme="@style/Theme.Material.Light.Dialog" + android:theme="@style/Theme.Material.DayNight.Dialog" android:label="@string/choose_account_label" android:process=":ui"> </activity> <activity android:name="android.accounts.ChooseAccountTypeActivity" android:excludeFromRecents="true" - android:theme="@style/Theme.Material.Light.Dialog" + android:theme="@style/Theme.Material.DayNight.Dialog" android:label="@string/choose_account_label" android:process=":ui"> </activity> @@ -2534,19 +2536,19 @@ <activity android:name="android.accounts.CantAddAccountActivity" android:excludeFromRecents="true" android:exported="true" - android:theme="@style/Theme.Material.Light.Dialog.NoActionBar" + android:theme="@style/Theme.Material.DayNight.Dialog.NoActionBar" android:process=":ui"> </activity> <activity android:name="android.accounts.GrantCredentialsPermissionActivity" android:excludeFromRecents="true" android:exported="true" - android:theme="@style/Theme.Material.Light.DialogWhenLarge" + android:theme="@style/Theme.Material.DayNight.DialogWhenLarge" android:process=":ui"> </activity> <activity android:name="android.content.SyncActivityTooManyDeletes" - android:theme="@style/Theme.Material.Light.Dialog" + android:theme="@style/Theme.Material.DayNight.Dialog" android:label="@string/sync_too_many_deletes" android:process=":ui"> </activity> @@ -2566,13 +2568,13 @@ </activity> <activity android:name="com.android.internal.app.NetInitiatedActivity" - android:theme="@style/Theme.Material.Light.Dialog.Alert" + android:theme="@style/Theme.Material.DayNight.Dialog.Alert" android:excludeFromRecents="true" android:process=":ui"> </activity> <activity android:name="com.android.internal.app.RestrictionsPinActivity" - android:theme="@style/Theme.Material.Light.Dialog.Alert" + android:theme="@style/Theme.Material.DayNight.Dialog.Alert" android:excludeFromRecents="true" android:windowSoftInputMode="adjustPan" android:process=":ui"> diff --git a/core/res/res/anim/ic_checkbox_unchecked_box_inner_merged_animation.xml b/core/res/res/anim/ic_checkbox_to_checked_box_inner_merged_animation.xml index b5ad5e9d..e522453 100644 --- a/core/res/res/anim/ic_checkbox_unchecked_box_inner_merged_animation.xml +++ b/core/res/res/anim/ic_checkbox_to_checked_box_inner_merged_animation.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2015 The Android Open Source Project +<!-- Copyright (C) 2015 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. @@ -15,8 +14,7 @@ limitations under the License. --> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > +<set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator android:duration="166" android:propertyName="pathData" @@ -24,8 +22,7 @@ android:valueTo="M 0.0,-0.05 l 0.0,0.0 c 0.02761423749,0.0 0.05,0.02238576251 0.05,0.05 l 0.0,0.0 c 0.0,0.02761423749 -0.02238576251,0.05 -0.05,0.05 l 0.0,0.0 c -0.02761423749,0.0 -0.05,-0.02238576251 -0.05,-0.05 l 0.0,0.0 c 0.0,-0.02761423749 0.02238576251,-0.05 0.05,-0.05 Z M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" android:valueType="pathType" android:interpolator="@interpolator/ic_checkbox_unchecked_animation_interpolator_1" /> - <set - android:ordering="sequentially" > + <set android:ordering="sequentially"> <objectAnimator android:duration="166" android:propertyName="fillAlpha" diff --git a/core/res/res/anim/ic_checkbox_unchecked_box_outer_merged_animation.xml b/core/res/res/anim/ic_checkbox_to_checked_box_outer_merged_animation.xml index 066971a..628e967 100644 --- a/core/res/res/anim/ic_checkbox_unchecked_box_outer_merged_animation.xml +++ b/core/res/res/anim/ic_checkbox_to_checked_box_outer_merged_animation.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2015 The Android Open Source Project +<!-- Copyright (C) 2015 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. @@ -15,10 +14,8 @@ limitations under the License. --> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <set android:ordering="sequentially"> <objectAnimator android:duration="200" android:propertyName="pathData" @@ -34,8 +31,7 @@ android:valueType="pathType" android:interpolator="@interpolator/ic_checkbox_unchecked_animation_interpolator_1" /> </set> - <set - android:ordering="sequentially" > + <set android:ordering="sequentially"> <objectAnimator android:duration="166" android:propertyName="fillAlpha" diff --git a/core/res/res/anim/ic_checkbox_unchecked_icon_null_animation.xml b/core/res/res/anim/ic_checkbox_to_checked_icon_null_animation.xml index fc40d47..6fa3fd5 100644 --- a/core/res/res/anim/ic_checkbox_unchecked_icon_null_animation.xml +++ b/core/res/res/anim/ic_checkbox_to_checked_icon_null_animation.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2015 The Android Open Source Project +<!-- Copyright (C) 2015 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. @@ -15,10 +14,8 @@ limitations under the License. --> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <set android:ordering="sequentially"> <objectAnimator android:duration="200" android:propertyName="scaleX" @@ -32,8 +29,7 @@ android:valueTo="0.2" android:interpolator="@interpolator/ic_checkbox_unchecked_animation_interpolator_1" /> </set> - <set - android:ordering="sequentially" > + <set android:ordering="sequentially"> <objectAnimator android:duration="200" android:propertyName="scaleY" diff --git a/core/res/res/anim/ic_checkbox_checked_box_inner_merged_animation.xml b/core/res/res/anim/ic_checkbox_to_unchecked_box_inner_merged_animation.xml index 7be32af..d35b426 100644 --- a/core/res/res/anim/ic_checkbox_checked_box_inner_merged_animation.xml +++ b/core/res/res/anim/ic_checkbox_to_unchecked_box_inner_merged_animation.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2015 The Android Open Source Project +<!-- Copyright (C) 2015 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. @@ -15,10 +14,8 @@ limitations under the License. --> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <set android:ordering="sequentially"> <objectAnimator android:duration="166" android:propertyName="pathData" @@ -34,8 +31,7 @@ android:valueType="pathType" android:interpolator="@interpolator/ic_checkbox_checked_animation_interpolator_1" /> </set> - <set - android:ordering="sequentially" > + <set android:ordering="sequentially"> <objectAnimator android:duration="133" android:propertyName="fillAlpha" diff --git a/core/res/res/anim/ic_checkbox_checked_check_path_merged_animation.xml b/core/res/res/anim/ic_checkbox_to_unchecked_check_path_merged_animation.xml index fcba2c8..a5d814e 100644 --- a/core/res/res/anim/ic_checkbox_checked_check_path_merged_animation.xml +++ b/core/res/res/anim/ic_checkbox_to_unchecked_check_path_merged_animation.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2015 The Android Open Source Project +<!-- Copyright (C) 2015 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. @@ -15,8 +14,7 @@ limitations under the License. --> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > +<set xmlns:android="http://schemas.android.com/apk/res/android"> <objectAnimator android:duration="166" android:propertyName="pathData" @@ -24,8 +22,7 @@ android:valueTo="M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z M 0.0,1.42500305176 c 0.0,0.0 -1.4234161377,-1.40159606934 -1.4234161377,-1.40159606934 c 0.0,0.0 1.41409301758,-1.41409301758 1.41409301758,-1.41409301758 c 0.0,0.0 0.00932312011719,-0.0124053955078 0.00932312011719,-0.0124053955078 c 0.0,0.0 0.0234069824219,-0.0235137939453 0.0234069824219,-0.0235137939453 c 0.0,0.0 1.41409301758,1.41409301758 1.41409301758,1.41409301758 c 0.0,0.0 -1.4375,1.43751525879 -1.4375,1.43751525879 Z" android:valueType="pathType" android:interpolator="@interpolator/ic_checkbox_checked_animation_interpolator_1" /> - <set - android:ordering="sequentially" > + <set android:ordering="sequentially" > <objectAnimator android:duration="133" android:propertyName="fillAlpha" diff --git a/core/res/res/anim/ic_checkbox_checked_icon_null_animation.xml b/core/res/res/anim/ic_checkbox_to_unchecked_icon_null_animation.xml index 312003f..0f07b0e 100644 --- a/core/res/res/anim/ic_checkbox_checked_icon_null_animation.xml +++ b/core/res/res/anim/ic_checkbox_to_unchecked_icon_null_animation.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2015 The Android Open Source Project +<!-- Copyright (C) 2015 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. @@ -15,10 +14,8 @@ limitations under the License. --> -<set - xmlns:android="http://schemas.android.com/apk/res/android" > - <set - android:ordering="sequentially" > +<set xmlns:android="http://schemas.android.com/apk/res/android"> + <set android:ordering="sequentially"> <objectAnimator android:duration="166" android:propertyName="scaleX" @@ -32,8 +29,7 @@ android:valueTo="0.2" android:interpolator="@interpolator/ic_checkbox_checked_animation_interpolator_1" /> </set> - <set - android:ordering="sequentially" > + <set android:ordering="sequentially"> <objectAnimator android:duration="166" android:propertyName="scaleY" diff --git a/core/res/res/drawable-hdpi/text_cursor_mtrl_alpha.9.png b/core/res/res/drawable-hdpi/text_cursor_mtrl_alpha.9.png Binary files differdeleted file mode 100644 index 0179433..0000000 --- a/core/res/res/drawable-hdpi/text_cursor_mtrl_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/text_cursor_mtrl_alpha.9.png b/core/res/res/drawable-mdpi/text_cursor_mtrl_alpha.9.png Binary files differdeleted file mode 100644 index e5760be..0000000 --- a/core/res/res/drawable-mdpi/text_cursor_mtrl_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable-xhdpi/text_cursor_mtrl_alpha.9.png b/core/res/res/drawable-xhdpi/text_cursor_mtrl_alpha.9.png Binary files differdeleted file mode 100644 index 3939214..0000000 --- a/core/res/res/drawable-xhdpi/text_cursor_mtrl_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/text_cursor_mtrl_alpha.9.png b/core/res/res/drawable-xxhdpi/text_cursor_mtrl_alpha.9.png Binary files differdeleted file mode 100644 index 432c385..0000000 --- a/core/res/res/drawable-xxhdpi/text_cursor_mtrl_alpha.9.png +++ /dev/null diff --git a/core/res/res/drawable/btn_check_material_anim.xml b/core/res/res/drawable/btn_check_material_anim.xml index 7cb1b89..710a291 100644 --- a/core/res/res/drawable/btn_check_material_anim.xml +++ b/core/res/res/drawable/btn_check_material_anim.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2014 The Android Open Source Project +<!-- Copyright (C) 2015 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. @@ -15,24 +15,19 @@ --> <animated-selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item - android:id="@+id/on" + android:id="@+id/checked" android:state_checked="true" android:drawable="@drawable/ic_checkbox_checked" /> - <item - android:id="@+id/off" + android:id="@+id/unchecked" android:drawable="@drawable/ic_checkbox_unchecked" /> - <transition - android:fromId="@+id/off" - android:toId="@+id/on" - android:drawable="@drawable/ic_checkbox_unchecked_animation" /> - + android:fromId="@+id/unchecked" + android:toId="@+id/checked" + android:drawable="@drawable/ic_checkbox_unchecked_to_checked_animation" /> <transition - android:fromId="@+id/on" - android:toId="@+id/off" - android:drawable="@drawable/ic_checkbox_checked_animation" /> - + android:fromId="@+id/checked" + android:toId="@+id/unchecked" + android:drawable="@drawable/ic_checkbox_checked_to_unchecked_animation" /> </animated-selector> diff --git a/core/res/res/drawable/ic_checkbox_checked.xml b/core/res/res/drawable/ic_checkbox_checked.xml index cc7b5df..ecde414 100644 --- a/core/res/res/drawable/ic_checkbox_checked.xml +++ b/core/res/res/drawable/ic_checkbox_checked.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2015 The Android Open Source Project +<!-- Copyright (C) 2015 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. @@ -15,24 +14,23 @@ limitations under the License. --> -<vector - xmlns:android="http://schemas.android.com/apk/res/android" - android:name="ic_checkbox_checked" - android:width="32dp" - android:viewportWidth="48" - android:height="32dp" - android:viewportHeight="48" - android:tint="?attr/colorControlActivated" > +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:name="ic_checkbox_checked" + android:width="32dp" + android:viewportWidth="48" + android:height="32dp" + android:viewportHeight="48" + android:tint="@color/control_checkable_material"> <group android:name="icon_null" android:translateX="24" android:translateY="24" android:scaleX="0.2" - android:scaleY="0.2" > + android:scaleY="0.2"> <group android:name="check" android:scaleX="7.5" - android:scaleY="7.5" > + android:scaleY="7.5"> <path android:name="check_path_merged" android:pathData="M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z M -2.0,5.00001525879 c 0.0,0.0 -5.0,-5.00001525879 -5.0,-5.00001525879 c 0.0,0.0 1.41409301758,-1.41409301758 1.41409301758,-1.41409301758 c 0.0,0.0 3.58590698242,3.58601379395 3.58590698242,3.58601379395 c 0.0,0.0 7.58590698242,-7.58601379395 7.58590698242,-7.58601379395 c 0.0,0.0 1.41409301758,1.41409301758 1.41409301758,1.41409301758 c 0.0,0.0 -9.0,9.00001525879 -9.0,9.00001525879 Z" @@ -41,7 +39,7 @@ <group android:name="box_dilate" android:scaleX="7.5" - android:scaleY="7.5" > + android:scaleY="7.5"> <path android:name="box_inner_merged" android:pathData="M 0.0,-1.0 l 0.0,0.0 c 0.5522847498,0.0 1.0,0.4477152502 1.0,1.0 l 0.0,0.0 c 0.0,0.5522847498 -0.4477152502,1.0 -1.0,1.0 l 0.0,0.0 c -0.5522847498,0.0 -1.0,-0.4477152502 -1.0,-1.0 l 0.0,0.0 c 0.0,-0.5522847498 0.4477152502,-1.0 1.0,-1.0 Z M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" diff --git a/core/res/res/drawable/ic_checkbox_checked_animation.xml b/core/res/res/drawable/ic_checkbox_checked_to_unchecked_animation.xml index af5eeee..fad2233 100644 --- a/core/res/res/drawable/ic_checkbox_checked_animation.xml +++ b/core/res/res/drawable/ic_checkbox_checked_to_unchecked_animation.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2015 The Android Open Source Project +<!-- Copyright (C) 2015 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. @@ -15,16 +14,15 @@ limitations under the License. --> -<animated-vector - xmlns:android="http://schemas.android.com/apk/res/android" - android:drawable="@drawable/ic_checkbox_checked" > +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/ic_checkbox_checked"> <target android:name="icon_null" - android:animation="@anim/ic_checkbox_checked_icon_null_animation" /> + android:animation="@anim/ic_checkbox_to_unchecked_icon_null_animation" /> <target android:name="check_path_merged" - android:animation="@anim/ic_checkbox_checked_check_path_merged_animation" /> + android:animation="@anim/ic_checkbox_to_unchecked_check_path_merged_animation" /> <target android:name="box_inner_merged" - android:animation="@anim/ic_checkbox_checked_box_inner_merged_animation" /> + android:animation="@anim/ic_checkbox_to_unchecked_box_inner_merged_animation" /> </animated-vector> diff --git a/core/res/res/drawable/ic_checkbox_unchecked.xml b/core/res/res/drawable/ic_checkbox_unchecked.xml index 410f0bc..3329b46 100644 --- a/core/res/res/drawable/ic_checkbox_unchecked.xml +++ b/core/res/res/drawable/ic_checkbox_unchecked.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2015 The Android Open Source Project +<!-- Copyright (C) 2015 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. @@ -15,24 +14,23 @@ limitations under the License. --> -<vector - xmlns:android="http://schemas.android.com/apk/res/android" - android:name="ic_checkbox_unchecked" - android:width="32dp" - android:viewportWidth="48" - android:height="32dp" - android:viewportHeight="48" - android:tint="?attr/colorControlNormal" > +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:name="ic_checkbox_unchecked" + android:width="32dp" + android:viewportWidth="48" + android:height="32dp" + android:viewportHeight="48" + android:tint="@color/control_checkable_material"> <group android:name="icon_null" android:translateX="24" android:translateY="24" android:scaleX="0.2" - android:scaleY="0.2" > + android:scaleY="0.2"> <group android:name="check" android:scaleX="7.5" - android:scaleY="7.5" > + android:scaleY="7.5"> <path android:name="box_outer_merged" android:pathData="M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z M -2.0,5.00001525879 c 0.0,0.0 -1.4234161377,-1.40159606934 -1.4234161377,-1.40159606934 c 0.0,0.0 1.41409301758,-1.41409301758 1.41409301758,-1.41409301758 c 0.0,0.0 0.00932312011719,-0.0124053955078 0.00932312011719,-0.0124053955078 c 0.0,0.0 0.0234069824219,-0.0235137939453 0.0234069824219,-0.0235137939453 c 0.0,0.0 1.41409301758,1.41409301758 1.41409301758,1.41409301758 c 0.0,0.0 -1.4375,1.43751525879 -1.4375,1.43751525879 Z" @@ -42,7 +40,7 @@ <group android:name="box_dilate" android:scaleX="7.5" - android:scaleY="7.5" > + android:scaleY="7.5"> <path android:name="box_inner_merged" android:pathData="M -7.0,-7.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,14.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-14.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z" diff --git a/core/res/res/drawable/ic_checkbox_unchecked_animation.xml b/core/res/res/drawable/ic_checkbox_unchecked_to_checked_animation.xml index 605fce1..6835170 100644 --- a/core/res/res/drawable/ic_checkbox_unchecked_animation.xml +++ b/core/res/res/drawable/ic_checkbox_unchecked_to_checked_animation.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2015 The Android Open Source Project +<!-- Copyright (C) 2015 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. @@ -15,16 +14,15 @@ limitations under the License. --> -<animated-vector - xmlns:android="http://schemas.android.com/apk/res/android" - android:drawable="@drawable/ic_checkbox_unchecked" > +<animated-vector xmlns:android="http://schemas.android.com/apk/res/android" + android:drawable="@drawable/ic_checkbox_unchecked"> <target android:name="icon_null" - android:animation="@anim/ic_checkbox_unchecked_icon_null_animation" /> + android:animation="@anim/ic_checkbox_to_checked_icon_null_animation" /> <target android:name="box_outer_merged" - android:animation="@anim/ic_checkbox_unchecked_box_outer_merged_animation" /> + android:animation="@anim/ic_checkbox_to_checked_box_outer_merged_animation" /> <target android:name="box_inner_merged" - android:animation="@anim/ic_checkbox_unchecked_box_inner_merged_animation" /> + android:animation="@anim/ic_checkbox_to_checked_box_inner_merged_animation" /> </animated-vector> diff --git a/core/res/res/drawable/scroll_indicator_material.xml b/core/res/res/drawable/scroll_indicator_material.xml new file mode 100644 index 0000000..63cd584 --- /dev/null +++ b/core/res/res/drawable/scroll_indicator_material.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:tint="?attr/colorForeground"> + <solid android:color="#1f000000" /> + <size + android:height="1dp" + android:width="1dp" /> +</shape> diff --git a/core/res/res/drawable/text_cursor_material.xml b/core/res/res/drawable/text_cursor_material.xml index a350c47..0bedaa9 100644 --- a/core/res/res/drawable/text_cursor_material.xml +++ b/core/res/res/drawable/text_cursor_material.xml @@ -14,6 +14,15 @@ limitations under the License. --> -<nine-patch xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/text_cursor_mtrl_alpha" - android:tint="?attr/colorControlActivated" /> +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:inset="2dp"> + <shape + android:tint="?attr/colorControlActivated" + android:shape="rectangle"> + <size + android:height="2dp" + android:width="2dp" /> + <solid + android:color="@color/white" /> + </shape> +</inset> diff --git a/core/res/res/layout-land/time_picker_material.xml b/core/res/res/layout-land/time_picker_material.xml index 89c3749..4b544d2 100644 --- a/core/res/res/layout-land/time_picker_material.xml +++ b/core/res/res/layout-land/time_picker_material.xml @@ -16,7 +16,6 @@ --> <GridLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> @@ -27,8 +26,7 @@ android:layout_column="0" android:layout_row="0" android:layout_rowSpan="3" - android:layout_gravity="center|fill" - tools:background="@color/accent_material_light" /> + android:layout_gravity="center|fill" /> <RelativeLayout android:layout_width="wrap_content" @@ -56,20 +54,14 @@ android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" android:singleLine="true" android:ellipsize="none" - android:gravity="right" - tools:text="23" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:gravity="right" /> <TextView android:id="@+id/separator" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" - android:importantForAccessibility="no" - tools:text=":" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:importantForAccessibility="no" /> <!-- The minutes should always be to the right of the separator, regardless of the current locale's layout direction. --> @@ -80,10 +72,7 @@ android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" android:singleLine="true" android:ellipsize="none" - android:gravity="left" - tools:text="59" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:gravity="left" /> </LinearLayout> <!-- The layout alignment of this view will switch between toRightOf @@ -106,10 +95,7 @@ android:paddingTop="@dimen/timepicker_am_top_padding" android:lines="1" android:ellipsize="none" - android:includeFontPadding="false" - tools:text="AM" - tools:textSize="@dimen/timepicker_ampm_label_size" - tools:textColor="@color/white" /> + android:includeFontPadding="false" /> <CheckedTextView android:id="@+id/pm_label" @@ -121,10 +107,7 @@ android:paddingTop="@dimen/timepicker_pm_top_padding" android:lines="1" android:ellipsize="none" - android:includeFontPadding="false" - tools:text="PM" - tools:textSize="@dimen/timepicker_ampm_label_size" - tools:textColor="@color/white" /> + android:includeFontPadding="false" /> </LinearLayout> </RelativeLayout> diff --git a/core/res/res/layout/alert_dialog_button_bar_material.xml b/core/res/res/layout/alert_dialog_button_bar_material.xml index 1eea4e1..6e102f3 100644 --- a/core/res/res/layout/alert_dialog_button_bar_material.xml +++ b/core/res/res/layout/alert_dialog_button_bar_material.xml @@ -27,6 +27,7 @@ android:paddingTop="4dp" android:paddingBottom="4dp" android:gravity="bottom" + android:allowStacking="@bool/allow_stacked_button_bar" style="?attr/buttonBarStyle"> <Button @@ -53,4 +54,4 @@ style="?attr/buttonBarPositiveButtonStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" /> -</com.android.internal.widget.ButtonBarLayout>
\ No newline at end of file +</com.android.internal.widget.ButtonBarLayout> diff --git a/core/res/res/layout/alert_dialog_material.xml b/core/res/res/layout/alert_dialog_material.xml index bf1e383..95c2459 100644 --- a/core/res/res/layout/alert_dialog_material.xml +++ b/core/res/res/layout/alert_dialog_material.xml @@ -24,52 +24,51 @@ <include layout="@layout/alert_dialog_title_material" /> - <FrameLayout android:id="@+id/contentPanel" + <FrameLayout + android:id="@+id/contentPanel" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:minHeight="48dp"> - <View android:id="@+id/scrollIndicatorUp" - android:visibility="gone" - android:layout_width="match_parent" - android:layout_height="1dp" - android:layout_gravity="top" - android:background="@drawable/list_divider_material"/> - <ScrollView android:id="@+id/scrollView" + + <ScrollView + android:id="@+id/scrollView" android:layout_width="match_parent" android:layout_height="wrap_content" + android:paddingTop="@dimen/dialog_padding_top_material" android:clipToPadding="false"> + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> - <TextView android:id="@+id/message" - style="@style/TextAppearance.Material.Subhead" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingStart="?attr/dialogPreferredPadding" - android:paddingTop="@dimen/dialog_padding_top_material" - android:paddingEnd="?attr/dialogPreferredPadding" /> - <Space android:id="@+id/textSpacerNoButtons" - android:visibility="gone" - android:layout_width="0dp" - android:layout_height="@dimen/dialog_padding_top_material" /> + + <TextView + android:id="@+id/message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingEnd="?attr/dialogPreferredPadding" + android:paddingStart="?attr/dialogPreferredPadding" + style="@style/TextAppearance.Material.Subhead" /> + + <Space + android:id="@+id/textSpacerNoButtons" + android:visibility="gone" + android:layout_width="0dp" + android:layout_height="@dimen/dialog_padding_top_material" /> </LinearLayout> </ScrollView> - <View android:id="@+id/scrollIndicatorDown" - android:visibility="gone" - android:layout_width="match_parent" - android:layout_height="1dp" - android:layout_gravity="bottom" - android:background="@drawable/list_divider_material"/> </FrameLayout> - <FrameLayout android:id="@+id/customPanel" + <FrameLayout + android:id="@+id/customPanel" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:minHeight="48dp"> - <FrameLayout android:id="@+id/custom" + + <FrameLayout + android:id="@+id/custom" android:layout_width="match_parent" android:layout_height="wrap_content" /> </FrameLayout> diff --git a/core/res/res/layout/date_picker_header_material.xml b/core/res/res/layout/date_picker_header_material.xml index 2150341..a4388f6 100644 --- a/core/res/res/layout/date_picker_header_material.xml +++ b/core/res/res/layout/date_picker_header_material.xml @@ -16,17 +16,13 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" android:id="@+id/date_picker_header" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="18dp" android:paddingStart="?attr/dialogPreferredPadding" android:paddingEnd="?attr/dialogPreferredPadding" - android:orientation="vertical" - tools:background="@color/accent_material_light" - tools:paddingStart="24dp" - tools:paddingEnd="24dp"> + android:orientation="vertical"> <!-- Top padding should stay on this view so that the touch target is a bit larger. --> @@ -35,10 +31,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="16dp" - android:textAppearance="@style/TextAppearance.Material.DatePicker.YearLabel" - tools:text="2015" - tools:textSize="@dimen/date_picker_year_label_size" - tools:textColor="@color/white" /> + android:textAppearance="@style/TextAppearance.Material.DatePicker.YearLabel" /> <TextView android:id="@+id/date_picker_header_date" @@ -47,9 +40,6 @@ android:textAppearance="@style/TextAppearance.Material.DatePicker.DateLabel" android:gravity="start" android:maxLines="2" - android:ellipsize="none" - tools:text="Thu, Sep 30" - tools:textSize="@dimen/date_picker_date_label_size" - tools:textColor="@color/white" /> + android:ellipsize="none" /> </LinearLayout> diff --git a/core/res/res/layout/time_picker_header_material.xml b/core/res/res/layout/time_picker_header_material.xml index be9e443..3f5e300 100644 --- a/core/res/res/layout/time_picker_header_material.xml +++ b/core/res/res/layout/time_picker_header_material.xml @@ -18,13 +18,11 @@ <!-- This layout is duplicated in land/time_picker_material.xml, so any changes made here need to be manually copied over. --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" android:id="@+id/time_header" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" - android:padding="@dimen/timepicker_separator_padding" - tools:background="@color/accent_material_light"> + android:padding="@dimen/timepicker_separator_padding"> <!-- The hour should always be to the left of the separator, regardless of the current locale's layout direction. --> @@ -37,10 +35,7 @@ android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" android:singleLine="true" android:ellipsize="none" - android:gravity="right" - tools:text="23" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:gravity="right" /> <TextView android:id="@+id/separator" @@ -50,10 +45,7 @@ android:layout_marginRight="@dimen/timepicker_separator_padding" android:layout_centerInParent="true" android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" - android:importantForAccessibility="no" - tools:text=":" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:importantForAccessibility="no" /> <!-- The minutes should always be to the left of the separator, regardless of the current locale's layout direction. --> @@ -66,10 +58,7 @@ android:textAppearance="@style/TextAppearance.Material.TimePicker.TimeLabel" android:singleLine="true" android:ellipsize="none" - android:gravity="left" - tools:text="59" - tools:textSize="@dimen/timepicker_time_label_size" - tools:textColor="@color/white" /> + android:gravity="left" /> <!-- The layout alignment of this view will switch between toRightOf @id/minutes and toLeftOf @id/hours depending on the locale. --> @@ -90,10 +79,7 @@ android:paddingTop="@dimen/timepicker_am_top_padding" android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel" android:lines="1" - android:ellipsize="none" - tools:text="AM" - tools:textSize="@dimen/timepicker_ampm_label_size" - tools:textColor="@color/white" /> + android:ellipsize="none" /> <CheckedTextView android:id="@+id/pm_label" android:layout_width="wrap_content" @@ -103,9 +89,6 @@ android:paddingTop="@dimen/timepicker_pm_top_padding" android:textAppearance="@style/TextAppearance.Material.TimePicker.AmPmLabel" android:lines="1" - android:ellipsize="none" - tools:text="PM" - tools:textSize="@dimen/timepicker_ampm_label_size" - tools:textColor="@color/white" /> + android:ellipsize="none" /> </LinearLayout> </RelativeLayout> diff --git a/core/res/res/transition/popup_window_enter.xml b/core/res/res/transition/popup_window_enter.xml index 38c41f0..c4c8dac 100644 --- a/core/res/res/transition/popup_window_enter.xml +++ b/core/res/res/transition/popup_window_enter.xml @@ -16,17 +16,14 @@ <transitionSet xmlns:android="http://schemas.android.com/apk/res/android" android:transitionOrdering="together"> - <!-- Start from location of epicenter, move to popup location. --> - <transition - class="com.android.internal.transition.EpicenterTranslate" - android:duration="300" /> - <!-- Start from size of epicenter, expand to full width/height. --> <transition - class="com.android.internal.transition.EpicenterClipReveal" - android:centerClipBounds="true" - android:duration="300" /> + class="com.android.internal.transition.EpicenterTranslateClipReveal" + android:duration="250" /> <!-- Quickly fade in. --> - <fade android:duration="100" /> + <fade + android:duration="100" + android:fromAlpha="0.1" + android:toAlpha="1.0" /> </transitionSet> diff --git a/core/res/res/values-mcc311-mnc480/config.xml b/core/res/res/values-mcc311-mnc480/config.xml index 39ea2bf..a986b75 100755 --- a/core/res/res/values-mcc311-mnc480/config.xml +++ b/core/res/res/values-mcc311-mnc480/config.xml @@ -46,7 +46,7 @@ <!-- Flag specifying whether VT should be available for carrier: independent of carrier provisioning. If false: hard disabled. If true: then depends on carrier provisioning, availability etc --> - <bool name="config_carrier_vt_available">true</bool> + <bool name="config_carrier_vt_available">false</bool> <!-- Flag specifying whether VoLTE availability is based on provisioning --> <bool name="config_carrier_volte_provisioned">true</bool> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index fe5862b..eaa6278 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -1998,6 +1998,13 @@ <attr name="needsDefaultBackgrounds" format="boolean" /> </declare-styleable> + <!-- @hide --> + <declare-styleable name="ButtonBarLayout"> + <!-- Whether to automatically stack the buttons when there is not + enough space to lay them out side-by-side. --> + <attr name="allowStacking" format="boolean" /> + </declare-styleable> + <!-- Fragment animation class attributes. --> <declare-styleable name="FragmentAnimation"> <attr name="fragmentOpenEnterAnimation" format="reference" /> @@ -2715,6 +2722,28 @@ <enum name="add" value="16" /> </attr> + <!-- Defines which scroll indicators should be displayed when the view + can be scrolled. Multiple values may be combined using logical OR, + for example "top|bottom". --> + <attr name="scrollIndicators"> + <!-- No scroll indicators are displayed. --> + <flag name="none" value="0x0000" /> + <!-- Displays top scroll indicator when view can be scrolled up. --> + <flag name="top" value="0x0100" /> + <!-- Displays bottom scroll indicator when vew can be scrolled down. --> + <flag name="bottom" value="0x0200" /> + <!-- Displays left scroll indicator when vew can be scrolled left. --> + <flag name="left" value="0x0400" /> + <!-- Displays right scroll indicator when vew can be scrolled right. --> + <flag name="right" value="0x0800" /> + <!-- Displays right scroll indicator when vew can be scrolled in the + start direction. --> + <flag name="start" value="0x1000" /> + <!-- Displays right scroll indicator when vew can be scrolled in the + end direction. --> + <flag name="end" value="0x2000" /> + </attr> + </declare-styleable> <!-- Attributes that can be assigned to a tag for a particular View. --> @@ -5848,16 +5877,9 @@ </declare-styleable> <!-- @hide For internal use only. Use only as directed. --> - <declare-styleable name="EpicenterClipReveal"> - <attr name="centerClipBounds" format="boolean" /> + <declare-styleable name="EpicenterTranslateClipReveal"> <attr name="interpolatorX" format="reference" /> <attr name="interpolatorY" format="reference" /> - </declare-styleable> - - <!-- @hide For internal use only. Use only as directed. --> - <declare-styleable name="EpicenterTranslate"> - <attr name="interpolatorX" /> - <attr name="interpolatorY" /> <attr name="interpolatorZ" format="reference" /> </declare-styleable> @@ -7306,6 +7328,9 @@ <!-- Flag indicating whether this voice interaction service is capable of handling the assist action. --> <attr name="supportsAssist" format="boolean" /> + <!-- Flag indicating whether this voice interaction service is capable of being launched + from the keyguard. --> + <attr name="supportsLaunchVoiceAssistFromKeyguard" format="boolean" /> </declare-styleable> <!-- Use <code>voice-enrollment-application</code> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 91c3d2e..297b302 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2623,7 +2623,6 @@ <public type="attr" name="fullBackupContent" /> <public type="style" name="Widget.Material.Button.Colored" /> - <public type="style" name="Theme.Material.DayNight" /> <public type="style" name="Theme.Material.DayNight.DarkActionBar" /> <public type="style" name="Theme.Material.DayNight.Dialog" /> @@ -2685,4 +2684,6 @@ <public type="attr" name="assistBlocked" /> <public type="attr" name="stylusButtonPressable" /> + <public type="attr" name="supportsLaunchVoiceAssistFromKeyguard" /> + <public type="attr" name="scrollIndicators" /> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index f36d448..55b32e1 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -255,6 +255,8 @@ <string-array name="wfcOperatorErrorNotificationMessages" /> <!-- Template for showing cellular network operator name while WFC is active --> <string name="wfcSpnFormat">%s</string> + <!-- Template for showing operator name for data connection while WFC is active --> + <string name="wfcDataSpnFormat">%s</string> <!-- WFC, summary for Disabled --> <string name="wifi_calling_off_summary">Off</string> <!-- WFC, summary for Wi-Fi Preferred --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index f8d276f..28ffbfa 100755 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -773,6 +773,7 @@ <java-symbol type="array" name="wfcOperatorErrorAlertMessages" /> <java-symbol type="array" name="wfcOperatorErrorNotificationMessages" /> <java-symbol type="string" name="wfcSpnFormat" /> + <java-symbol type="string" name="wfcDataSpnFormat" /> <java-symbol type="string" name="wifi_calling_off_summary" /> <java-symbol type="string" name="wfc_mode_wifi_preferred_summary" /> <java-symbol type="string" name="wfc_mode_cellular_preferred_summary" /> @@ -2179,8 +2180,6 @@ <java-symbol type="bool" name="config_defaultWindowFeatureContextMenu" /> <java-symbol type="layout" name="simple_account_item" /> - <java-symbol type="id" name="scrollIndicatorUp" /> - <java-symbol type="id" name="scrollIndicatorDown" /> <java-symbol type="array" name="config_sms_convert_destination_number_support" /> <java-symbol type="string" name="prohibit_manual_network_selection_in_gobal_mode" /> <java-symbol type="id" name="profile_button" /> @@ -2203,7 +2202,6 @@ <java-symbol type="string" name="usb_midi_peripheral_manufacturer_name" /> <java-symbol type="string" name="usb_midi_peripheral_product_name" /> - <java-symbol type="bool" name="allow_stacked_button_bar" /> <java-symbol type="id" name="spacer" /> <java-symbol type="xml" name="bookmarks" /> @@ -2257,4 +2255,5 @@ <java-symbol type="id" name="title_icon" /> <java-symbol type="id" name="day_picker_view_pager" /> <java-symbol type="layout" name="day_picker_content_material" /> + <java-symbol type="drawable" name="scroll_indicator_material" /> </resources> diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml index e679e0a..f02fed1 100644 --- a/core/res/res/values/themes_material.xml +++ b/core/res/res/values/themes_material.xml @@ -207,8 +207,8 @@ please see themes_device_defaults.xml. <!-- Scrollbar attributes --> <item name="scrollbarFadeDuration">250</item> - <item name="scrollbarDefaultDelayBeforeFade">300</item> - <item name="scrollbarSize">10dip</item> + <item name="scrollbarDefaultDelayBeforeFade">400</item> + <item name="scrollbarSize">10dp</item> <item name="scrollbarThumbHorizontal">@drawable/scrollbar_handle_material</item> <item name="scrollbarThumbVertical">@drawable/scrollbar_handle_material</item> <item name="scrollbarTrackHorizontal">@null</item> @@ -563,8 +563,8 @@ please see themes_device_defaults.xml. <!-- Scrollbar attributes --> <item name="scrollbarFadeDuration">250</item> - <item name="scrollbarDefaultDelayBeforeFade">300</item> - <item name="scrollbarSize">10dip</item> + <item name="scrollbarDefaultDelayBeforeFade">400</item> + <item name="scrollbarSize">10dp</item> <item name="scrollbarThumbHorizontal">@drawable/scrollbar_handle_material</item> <item name="scrollbarThumbVertical">@drawable/scrollbar_handle_material</item> <item name="scrollbarTrackHorizontal">@null</item> diff --git a/core/tests/coretests/src/android/util/FloatMathTest.java b/core/tests/coretests/src/android/util/FloatMathTest.java deleted file mode 100644 index f479e2b..0000000 --- a/core/tests/coretests/src/android/util/FloatMathTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.util; - -import junit.framework.TestCase; -import android.test.suitebuilder.annotation.SmallTest; - -public class FloatMathTest extends TestCase { - - @SmallTest - public void testSqrt() { - assertEquals(7, FloatMath.sqrt(49), 0); - assertEquals(10, FloatMath.sqrt(100), 0); - assertEquals(0, FloatMath.sqrt(0), 0); - assertEquals(1, FloatMath.sqrt(1), 0); - } - - @SmallTest - public void testFloor() { - assertEquals(78, FloatMath.floor(78.89f), 0); - assertEquals(-79, FloatMath.floor(-78.89f), 0); - } - - @SmallTest - public void testCeil() { - assertEquals(79, FloatMath.ceil(78.89f), 0); - assertEquals(-78, FloatMath.ceil(-78.89f), 0); - } - - @SmallTest - public void testSin() { - assertEquals(0.0, FloatMath.sin(0), 0); - assertEquals(0.8414709848078965f, FloatMath.sin(1), 0); - } - - @SmallTest - public void testCos() { - assertEquals(1.0f, FloatMath.cos(0), 0); - assertEquals(0.5403023058681398f, FloatMath.cos(1), 0); - } -} diff --git a/core/tests/coretests/src/com/android/internal/util/CallbackRegistryTest.java b/core/tests/coretests/src/com/android/internal/util/CallbackRegistryTest.java new file mode 100644 index 0000000..c53f4cc --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/util/CallbackRegistryTest.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.util; + +import junit.framework.TestCase; + +import org.junit.Test; + +import java.util.ArrayList; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class CallbackRegistryTest extends TestCase { + + final Integer callback1 = 1; + final Integer callback2 = 2; + final Integer callback3 = 3; + CallbackRegistry<Integer, CallbackRegistryTest, Integer> registry; + int notify1; + int notify2; + int notify3; + int[] deepNotifyCount = new int[300]; + Integer argValue; + + private void addNotifyCount(Integer callback) { + if (callback == callback1) { + notify1++; + } else if (callback == callback2) { + notify2++; + } else if (callback == callback3) { + notify3++; + } + deepNotifyCount[callback]++; + } + + public void testAddListener() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg, Integer arg2) { + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + Integer callback = 0; + + assertNotNull(registry.copyListeners()); + assertEquals(0, registry.copyListeners().size()); + + registry.add(callback); + ArrayList<Integer> callbacks = registry.copyListeners(); + assertEquals(1, callbacks.size()); + assertEquals(callback, callbacks.get(0)); + + registry.add(callback); + callbacks = registry.copyListeners(); + assertEquals(1, callbacks.size()); + assertEquals(callback, callbacks.get(0)); + + Integer otherListener = 1; + registry.add(otherListener); + callbacks = registry.copyListeners(); + assertEquals(2, callbacks.size()); + assertEquals(callback, callbacks.get(0)); + assertEquals(otherListener, callbacks.get(1)); + + registry.remove(callback); + registry.add(callback); + callbacks = registry.copyListeners(); + assertEquals(2, callbacks.size()); + assertEquals(callback, callbacks.get(1)); + assertEquals(otherListener, callbacks.get(0)); + } + + public void testSimpleNotify() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + assertEquals(arg1, (int) arg); + addNotifyCount(callback); + argValue = arg; + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + registry.add(callback2); + Integer arg = 1; + registry.notifyCallbacks(this, arg, arg); + assertEquals(arg, argValue); + assertEquals(1, notify2); + } + + public void testRemoveWhileNotifying() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + if (callback == callback1) { + registry.remove(callback1); + registry.remove(callback2); + } + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + registry.add(callback1); + registry.add(callback2); + registry.add(callback3); + registry.notifyCallbacks(this, 0, null); + assertEquals(1, notify1); + assertEquals(1, notify2); + assertEquals(1, notify3); + + ArrayList<Integer> callbacks = registry.copyListeners(); + assertEquals(1, callbacks.size()); + assertEquals(callback3, callbacks.get(0)); + } + + public void testDeepRemoveWhileNotifying() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + registry.remove(callback); + registry.notifyCallbacks(CallbackRegistryTest.this, arg1, null); + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + registry.add(callback1); + registry.add(callback2); + registry.add(callback3); + registry.notifyCallbacks(this, 0, null); + assertEquals(1, notify1); + assertEquals(2, notify2); + assertEquals(3, notify3); + + ArrayList<Integer> callbacks = registry.copyListeners(); + assertEquals(0, callbacks.size()); + } + + public void testAddRemovedListener() { + + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + if (callback == callback1) { + registry.remove(callback2); + } else if (callback == callback3) { + registry.add(callback2); + } + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + + registry.add(callback1); + registry.add(callback2); + registry.add(callback3); + registry.notifyCallbacks(this, 0, null); + + ArrayList<Integer> callbacks = registry.copyListeners(); + assertEquals(3, callbacks.size()); + assertEquals(callback1, callbacks.get(0)); + assertEquals(callback3, callbacks.get(1)); + assertEquals(callback2, callbacks.get(2)); + assertEquals(1, notify1); + assertEquals(1, notify2); + assertEquals(1, notify3); + } + + public void testVeryDeepRemoveWhileNotifying() { + final Integer[] callbacks = new Integer[deepNotifyCount.length]; + for (int i = 0; i < callbacks.length; i++) { + callbacks[i] = i; + } + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + registry.remove(callback); + registry.remove(callbacks[callbacks.length - callback - 1]); + registry.notifyCallbacks(CallbackRegistryTest.this, arg1, null); + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + for (int i = 0; i < callbacks.length; i++) { + registry.add(callbacks[i]); + } + registry.notifyCallbacks(this, 0, null); + for (int i = 0; i < deepNotifyCount.length; i++) { + int expectedCount = Math.min(i + 1, deepNotifyCount.length - i); + assertEquals(expectedCount, deepNotifyCount[i]); + } + + ArrayList<Integer> callbackList = registry.copyListeners(); + assertEquals(0, callbackList.size()); + } + + public void testClear() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + for (int i = 0; i < deepNotifyCount.length; i++) { + registry.add(i); + } + registry.clear(); + + ArrayList<Integer> callbackList = registry.copyListeners(); + assertEquals(0, callbackList.size()); + + registry.notifyCallbacks(this, 0, null); + for (int i = 0; i < deepNotifyCount.length; i++) { + assertEquals(0, deepNotifyCount[i]); + } + } + + public void testNestedClear() { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg1, Integer arg) { + addNotifyCount(callback); + registry.clear(); + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + for (int i = 0; i < deepNotifyCount.length; i++) { + registry.add(i); + } + registry.notifyCallbacks(this, 0, null); + for (int i = 0; i < deepNotifyCount.length; i++) { + assertEquals(1, deepNotifyCount[i]); + } + + ArrayList<Integer> callbackList = registry.copyListeners(); + assertEquals(0, callbackList.size()); + } + + public void testIsEmpty() throws Exception { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg, Integer arg2) { + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + Integer callback = 0; + + assertTrue(registry.isEmpty()); + registry.add(callback); + assertFalse(registry.isEmpty()); + } + + public void testClone() throws Exception { + CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer> notifier = + new CallbackRegistry.NotifierCallback<Integer, CallbackRegistryTest, Integer>() { + @Override + public void onNotifyCallback(Integer callback, CallbackRegistryTest sender, + int arg, Integer arg2) { + } + }; + registry = new CallbackRegistry<Integer, CallbackRegistryTest, Integer>(notifier); + + assertTrue(registry.isEmpty()); + CallbackRegistry<Integer, CallbackRegistryTest, Integer> registry2 = registry.clone(); + Integer callback = 0; + registry.add(callback); + assertFalse(registry.isEmpty()); + assertTrue(registry2.isEmpty()); + registry2 = registry.clone(); + assertFalse(registry2.isEmpty()); + } +} |