diff options
Diffstat (limited to 'core/java/android')
76 files changed, 3281 insertions, 2304 deletions
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java index cf1e8f3..197c1bd 100644 --- a/core/java/android/accounts/AccountManagerService.java +++ b/core/java/android/accounts/AccountManagerService.java @@ -217,10 +217,10 @@ public class AccountManagerService mAuthenticatorCache = authenticatorCache; mAuthenticatorCache.setListener(this, null /* Handler */); - UserAccounts accounts = initUser(0); - sThis.set(this); + UserAccounts accounts = initUser(0); + IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); intentFilter.addDataScheme("package"); @@ -231,6 +231,14 @@ public class AccountManagerService } }, intentFilter); + IntentFilter userFilter = new IntentFilter(); + userFilter.addAction(Intent.ACTION_USER_REMOVED); + mContext.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + onUserRemoved(intent); + } + }, userFilter); } private UserAccounts initUser(int userId) { @@ -347,6 +355,28 @@ public class AccountManagerService } } + private void onUserRemoved(Intent intent) { + int userId = intent.getIntExtra(Intent.EXTRA_USERID, -1); + if (userId < 1) return; + + UserAccounts accounts; + synchronized (mUsers) { + accounts = mUsers.get(userId); + mUsers.remove(userId); + } + if (accounts == null) { + File dbFile = new File(getDatabaseName(userId)); + dbFile.delete(); + return; + } + + synchronized (accounts.cacheLock) { + accounts.openHelper.close(); + File dbFile = new File(getDatabaseName(userId)); + dbFile.delete(); + } + } + private List<UserInfo> getAllUsers() { try { return AppGlobals.getPackageManager().getUsers(); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index ea32745..b277efb 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -3639,7 +3639,7 @@ public class Activity extends ContextThemeWrapper */ public void startActivityFromChild(Activity child, Intent intent, int requestCode) { - startActivityFromChild(child, intent, requestCode); + startActivityFromChild(child, intent, requestCode, null); } /** diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index d056b17..531a695 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -31,6 +31,7 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Point; import android.os.Binder; +import android.os.Bundle; import android.os.Debug; import android.os.Handler; import android.os.Parcel; @@ -816,6 +817,19 @@ public class ActivityManager { public static final int MOVE_TASK_NO_USER_ACTION = 0x00000002; /** + * Equivalent to calling {@link #moveTaskToFront(int, int, Bundle)} + * with a null options argument. + * + * @param taskId The identifier of the task to be moved, as found in + * {@link RunningTaskInfo} or {@link RecentTaskInfo}. + * @param flags Additional operational flags, 0 or more of + * {@link #MOVE_TASK_WITH_HOME}. + */ + public void moveTaskToFront(int taskId, int flags) { + moveTaskToFront(taskId, flags, null); + } + + /** * Ask that the task associated with a given task ID be moved to the * front of the stack, so it is now visible to the user. Requires that * the caller hold permission {@link android.Manifest.permission#REORDER_TASKS} @@ -825,10 +839,13 @@ public class ActivityManager { * {@link RunningTaskInfo} or {@link RecentTaskInfo}. * @param flags Additional operational flags, 0 or more of * {@link #MOVE_TASK_WITH_HOME}. + * @param options Additional options for the operation, either null or + * as per {@link Context#startActivity(Intent, android.os.Bundle) + * Context.startActivity(Intent, Bundle)}. */ - public void moveTaskToFront(int taskId, int flags) { + public void moveTaskToFront(int taskId, int flags, Bundle options) { try { - ActivityManagerNative.getDefault().moveTaskToFront(taskId, flags); + ActivityManagerNative.getDefault().moveTaskToFront(taskId, flags, options); } catch (RemoteException e) { // System dead, we will be dead too soon! } diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index a3cc352..c402329 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -510,7 +510,9 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM data.enforceInterface(IActivityManager.descriptor); int task = data.readInt(); int fl = data.readInt(); - moveTaskToFront(task, fl); + Bundle options = data.readInt() != 0 + ? Bundle.CREATOR.createFromParcel(data) : null; + moveTaskToFront(task, fl, options); reply.writeNoException(); return true; } @@ -1055,6 +1057,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case KILL_PROCESSES_BELOW_FOREGROUND_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + String reason = data.readString(); + boolean res = killProcessesBelowForeground(reason); + reply.writeNoException(); + reply.writeInt(res ? 1 : 0); + return true; + } + case START_RUNNING_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); String pkg = data.readString(); @@ -2134,13 +2145,19 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return list; } - public void moveTaskToFront(int task, int flags) throws RemoteException + public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeInt(task); data.writeInt(flags); + if (options != null) { + data.writeInt(1); + options.writeToParcel(data, 0); + } else { + data.writeInt(0); + } mRemote.transact(MOVE_TASK_TO_FRONT_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); @@ -2902,6 +2919,18 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return res; } + @Override + public boolean killProcessesBelowForeground(String reason) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(reason); + mRemote.transact(KILL_PROCESSES_BELOW_FOREGROUND_TRANSACTION, data, reply, 0); + boolean res = reply.readInt() != 0; + data.recycle(); + reply.recycle(); + return res; + } public void startRunning(String pkg, String cls, String action, String indata) throws RemoteException { Parcel data = Parcel.obtain(); diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 03bc338..c637df0 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -17,7 +17,13 @@ package android.app; import android.content.Context; +import android.graphics.Bitmap; import android.os.Bundle; +import android.os.Handler; +import android.os.IRemoteCallback; +import android.os.Message; +import android.os.RemoteException; +import android.view.View; /** * Helper class for building an options Bundle that can be used with @@ -32,6 +38,12 @@ public class ActivityOptions { public static final String KEY_PACKAGE_NAME = "android:packageName"; /** + * Type of animation that arguments specify. + * @hide + */ + public static final String KEY_ANIM_TYPE = "android:animType"; + + /** * Custom enter animation resource ID. * @hide */ @@ -43,10 +55,45 @@ public class ActivityOptions { */ public static final String KEY_ANIM_EXIT_RES_ID = "android:animExitRes"; + /** + * Bitmap for thumbnail animation. + * @hide + */ + public static final String KEY_ANIM_THUMBNAIL = "android:animThumbnail"; + + /** + * Start X position of thumbnail animation. + * @hide + */ + public static final String KEY_ANIM_START_X = "android:animStartX"; + + /** + * Start Y position of thumbnail animation. + * @hide + */ + public static final String KEY_ANIM_START_Y = "android:animStartY"; + + /** + * Callback for when animation is started. + * @hide + */ + public static final String KEY_ANIM_START_LISTENER = "android:animStartListener"; + + /** @hide */ + public static final int ANIM_NONE = 0; + /** @hide */ + public static final int ANIM_CUSTOM = 1; + /** @hide */ + public static final int ANIM_THUMBNAIL = 2; + private String mPackageName; - private boolean mIsCustomAnimation; + private int mAnimationType = ANIM_NONE; private int mCustomEnterResId; private int mCustomExitResId; + private Bitmap mThumbnail; + private int mStartX; + private int mStartY; + private IRemoteCallback mAnimationStartedListener; /** * Create an ActivityOptions specifying a custom animation to run when @@ -65,22 +112,79 @@ public class ActivityOptions { int enterResId, int exitResId) { ActivityOptions opts = new ActivityOptions(); opts.mPackageName = context.getPackageName(); - opts.mIsCustomAnimation = true; + opts.mAnimationType = ANIM_CUSTOM; opts.mCustomEnterResId = enterResId; opts.mCustomExitResId = exitResId; return opts; } + /** + * Callback for use with {@link ActivityOptions#makeThumbnailScaleUpAnimation} + * to find out when the given animation has started running. + */ + public interface OnAnimationStartedListener { + void onAnimationStarted(); + } + + /** + * Create an ActivityOptions specifying an animation where a thumbnail + * is scaled from a given position to the new activity window that is + * being started. + * + * @param source The View that this thumbnail is animating from. This + * defines the coordinate space for <var>startX</var> and <var>startY</var>. + * @param thumbnail The bitmap that will be shown as the initial thumbnail + * of the animation. + * @param startX The x starting location of the bitmap, in screen coordiantes. + * @param startY The y starting location of the bitmap, in screen coordinates. + * @param listener Optional OnAnimationStartedListener to find out when the + * requested animation has started running. If for some reason the animation + * is not executed, the callback will happen immediately. + * @return Returns a new ActivityOptions object that you can use to + * supply these options as the options Bundle when starting an activity. + */ + public static ActivityOptions makeThumbnailScaleUpAnimation(View source, + Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) { + ActivityOptions opts = new ActivityOptions(); + opts.mPackageName = source.getContext().getPackageName(); + opts.mAnimationType = ANIM_THUMBNAIL; + opts.mThumbnail = thumbnail; + int[] pts = new int[2]; + source.getLocationOnScreen(pts); + opts.mStartX = pts[0] + startX; + opts.mStartY = pts[1] + startY; + if (listener != null) { + final Handler h = source.getHandler(); + final OnAnimationStartedListener finalListener = listener; + opts.mAnimationStartedListener = new IRemoteCallback.Stub() { + @Override public void sendResult(Bundle data) throws RemoteException { + h.post(new Runnable() { + @Override public void run() { + finalListener.onAnimationStarted(); + } + }); + } + }; + } + return opts; + } + private ActivityOptions() { } /** @hide */ public ActivityOptions(Bundle opts) { mPackageName = opts.getString(KEY_PACKAGE_NAME); - if (opts.containsKey(KEY_ANIM_ENTER_RES_ID)) { - mIsCustomAnimation = true; + mAnimationType = opts.getInt(KEY_ANIM_TYPE); + if (mAnimationType == ANIM_CUSTOM) { mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0); mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 0); + } else if (mAnimationType == ANIM_THUMBNAIL) { + mThumbnail = (Bitmap)opts.getParcelable(KEY_ANIM_THUMBNAIL); + mStartX = opts.getInt(KEY_ANIM_START_X, 0); + mStartY = opts.getInt(KEY_ANIM_START_Y, 0); + mAnimationStartedListener = IRemoteCallback.Stub.asInterface( + opts.getIBinder(KEY_ANIM_START_LISTENER)); } } @@ -90,8 +194,8 @@ public class ActivityOptions { } /** @hide */ - public boolean isCustomAnimation() { - return mIsCustomAnimation; + public int getAnimationType() { + return mAnimationType; } /** @hide */ @@ -104,6 +208,43 @@ public class ActivityOptions { return mCustomExitResId; } + /** @hide */ + public Bitmap getThumbnail() { + return mThumbnail; + } + + /** @hide */ + public int getStartX() { + return mStartX; + } + + /** @hide */ + public int getStartY() { + return mStartY; + } + + /** @hide */ + public IRemoteCallback getOnAnimationStartListener() { + return mAnimationStartedListener; + } + + /** @hide */ + public void abort() { + if (mAnimationStartedListener != null) { + try { + mAnimationStartedListener.sendResult(null); + } catch (RemoteException e) { + } + } + } + + /** @hide */ + public static void abort(Bundle options) { + if (options != null) { + (new ActivityOptions(options)).abort(); + } + } + /** * Join the values in <var>otherOptions</var> in to this one. Any values * defined in <var>otherOptions</var> replace those in the base options. @@ -112,10 +253,27 @@ public class ActivityOptions { if (otherOptions.mPackageName != null) { mPackageName = otherOptions.mPackageName; } - if (otherOptions.mIsCustomAnimation) { - mIsCustomAnimation = true; - mCustomEnterResId = otherOptions.mCustomEnterResId; - mCustomExitResId = otherOptions.mCustomExitResId; + switch (otherOptions.mAnimationType) { + case ANIM_CUSTOM: + mAnimationType = otherOptions.mAnimationType; + mCustomEnterResId = otherOptions.mCustomEnterResId; + mCustomExitResId = otherOptions.mCustomExitResId; + mThumbnail = null; + mAnimationStartedListener = null; + break; + case ANIM_THUMBNAIL: + mAnimationType = otherOptions.mAnimationType; + mThumbnail = otherOptions.mThumbnail; + mStartX = otherOptions.mStartX; + mStartY = otherOptions.mStartY; + if (otherOptions.mAnimationStartedListener != null) { + try { + otherOptions.mAnimationStartedListener.sendResult(null); + } catch (RemoteException e) { + } + } + mAnimationStartedListener = otherOptions.mAnimationStartedListener; + break; } } @@ -132,9 +290,19 @@ public class ActivityOptions { if (mPackageName != null) { b.putString(KEY_PACKAGE_NAME, mPackageName); } - if (mIsCustomAnimation) { - b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId); - b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId); + switch (mAnimationType) { + case ANIM_CUSTOM: + b.putInt(KEY_ANIM_TYPE, mAnimationType); + b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId); + b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId); + break; + case ANIM_THUMBNAIL: + b.putInt(KEY_ANIM_TYPE, mAnimationType); + b.putParcelable(KEY_ANIM_THUMBNAIL, mThumbnail); + b.putInt(KEY_ANIM_START_X, mStartX); + b.putInt(KEY_ANIM_START_Y, mStartY); + b.putIBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener + != null ? mAnimationStartedListener.asBinder() : null); } return b; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 2a3e213..ab4e73d 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -404,6 +404,7 @@ public final class ActivityThread { try { fd.close(); } catch (IOException e) { + // Ignore } } return; @@ -412,6 +413,7 @@ public final class ActivityThread { try { profileFd.close(); } catch (IOException e) { + // Ignore } } profileFile = file; @@ -843,14 +845,13 @@ public final class ActivityThread { FileOutputStream fout = new FileOutputStream(fd); PrintWriter pw = new PrintWriter(fout); try { - return dumpMemInfo(pw, checkin, all, args); + return dumpMemInfo(pw, checkin, all); } finally { pw.flush(); } } - private Debug.MemoryInfo dumpMemInfo(PrintWriter pw, boolean checkin, boolean all, - String[] args) { + private Debug.MemoryInfo dumpMemInfo(PrintWriter pw, boolean checkin, boolean all) { long nativeMax = Debug.getNativeHeapSize() / 1024; long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024; long nativeFree = Debug.getNativeHeapFreeSize() / 1024; @@ -1458,17 +1459,6 @@ public final class ActivityThread { return dm; } - static Configuration applyConfigCompat(Configuration config, CompatibilityInfo compat) { - if (config == null) { - return null; - } - if (compat != null && !compat.supportsScreen()) { - config = new Configuration(config); - compat.applyToConfiguration(config); - } - return config; - } - private Configuration mMainThreadConfig = new Configuration(); Configuration applyConfigCompatMainThread(Configuration config, CompatibilityInfo compat) { if (config == null) { @@ -1586,7 +1576,7 @@ public final class ActivityThread { ApplicationInfo ai = null; try { ai = getPackageManager().getApplicationInfo(packageName, - PackageManager.GET_SHARED_LIBRARY_FILES); + PackageManager.GET_SHARED_LIBRARY_FILES, UserId.myUserId()); } catch (RemoteException e) { // Ignore } @@ -2509,7 +2499,7 @@ public final class ActivityThread { return r; } - final void cleanUpPendingRemoveWindows(ActivityClientRecord r) { + static final void cleanUpPendingRemoveWindows(ActivityClientRecord r) { if (r.mPendingRemoveWindow != null) { r.mPendingRemoveWindowManager.removeViewImmediate(r.mPendingRemoveWindow); IBinder wtoken = r.mPendingRemoveWindow.getWindowToken(); @@ -3437,15 +3427,12 @@ public final class ActivityThread { = new ArrayList<ComponentCallbacks2>(); if (mActivities.size() > 0) { - Iterator<ActivityClientRecord> it = mActivities.values().iterator(); - while (it.hasNext()) { - ActivityClientRecord ar = it.next(); + for (ActivityClientRecord ar : mActivities.values()) { Activity a = ar.activity; if (a != null) { Configuration thisConfig = applyConfigCompatMainThread(newConfig, ar.packageInfo.mCompatibilityInfo.getIfNeeded()); - if (!ar.activity.mFinished && (allActivities || - (a != null && !ar.paused))) { + if (!ar.activity.mFinished && (allActivities || !ar.paused)) { // If the activity is currently resumed, its configuration // needs to change right now. callbacks.add(a); @@ -3455,24 +3442,24 @@ public final class ActivityThread { // the activity manager may, before then, decide the // activity needs to be destroyed to handle its new // configuration. - if (DEBUG_CONFIGURATION) Slog.v(TAG, "Setting activity " - + ar.activityInfo.name + " newConfig=" + thisConfig); + if (DEBUG_CONFIGURATION) { + Slog.v(TAG, "Setting activity " + + ar.activityInfo.name + " newConfig=" + thisConfig); + } ar.newConfig = thisConfig; } } } } if (mServices.size() > 0) { - Iterator<Service> it = mServices.values().iterator(); - while (it.hasNext()) { - callbacks.add(it.next()); + for (Service service : mServices.values()) { + callbacks.add(service); } } synchronized (mProviderMap) { if (mLocalProviders.size() > 0) { - Iterator<ProviderClientRecord> it = mLocalProviders.values().iterator(); - while (it.hasNext()) { - callbacks.add(it.next().mLocalProvider); + for (ProviderClientRecord providerClientRecord : mLocalProviders.values()) { + callbacks.add(providerClientRecord.mLocalProvider); } } } @@ -3484,8 +3471,7 @@ public final class ActivityThread { return callbacks; } - private final void performConfigurationChanged( - ComponentCallbacks2 cb, Configuration config) { + private static void performConfigurationChanged(ComponentCallbacks2 cb, Configuration config) { // Only for Activity objects, check that they actually call up to their // superclass implementation. ComponentCallbacks2 is an interface, so // we check the runtime type and act accordingly. @@ -3692,7 +3678,7 @@ public final class ActivityThread { } } - final void handleDumpHeap(boolean managed, DumpHeapData dhd) { + static final void handleDumpHeap(boolean managed, DumpHeapData dhd) { if (managed) { try { Debug.dumpHprofData(dhd.path, dhd.fd.getFileDescriptor()); @@ -3769,7 +3755,7 @@ public final class ActivityThread { } final int N = callbacks.size(); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { callbacks.get(i).onTrimMemory(level); } WindowManagerImpl.getDefault().terminateEgl(); @@ -4057,9 +4043,7 @@ public final class ActivityThread { final ArrayList<IActivityManager.ContentProviderHolder> results = new ArrayList<IActivityManager.ContentProviderHolder>(); - Iterator<ProviderInfo> i = providers.iterator(); - while (i.hasNext()) { - ProviderInfo cpi = i.next(); + for (ProviderInfo cpi : providers) { StringBuilder buf = new StringBuilder(128); buf.append("Pub "); buf.append(cpi.authority); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 758ce09..0510de1 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -49,6 +49,7 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Process; import android.os.RemoteException; +import android.os.UserId; import android.util.Log; import java.lang.ref.WeakReference; @@ -67,7 +68,7 @@ final class ApplicationPackageManager extends PackageManager { public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException { try { - PackageInfo pi = mPM.getPackageInfo(packageName, flags); + PackageInfo pi = mPM.getPackageInfo(packageName, flags, UserId.myUserId()); if (pi != null) { return pi; } @@ -197,7 +198,7 @@ final class ApplicationPackageManager extends PackageManager { public ApplicationInfo getApplicationInfo(String packageName, int flags) throws NameNotFoundException { try { - ApplicationInfo ai = mPM.getApplicationInfo(packageName, flags); + ApplicationInfo ai = mPM.getApplicationInfo(packageName, flags, UserId.myUserId()); if (ai != null) { return ai; } @@ -212,7 +213,7 @@ final class ApplicationPackageManager extends PackageManager { public ActivityInfo getActivityInfo(ComponentName className, int flags) throws NameNotFoundException { try { - ActivityInfo ai = mPM.getActivityInfo(className, flags); + ActivityInfo ai = mPM.getActivityInfo(className, flags, UserId.myUserId()); if (ai != null) { return ai; } @@ -227,7 +228,7 @@ final class ApplicationPackageManager extends PackageManager { public ActivityInfo getReceiverInfo(ComponentName className, int flags) throws NameNotFoundException { try { - ActivityInfo ai = mPM.getReceiverInfo(className, flags); + ActivityInfo ai = mPM.getReceiverInfo(className, flags, UserId.myUserId()); if (ai != null) { return ai; } @@ -242,7 +243,7 @@ final class ApplicationPackageManager extends PackageManager { public ServiceInfo getServiceInfo(ComponentName className, int flags) throws NameNotFoundException { try { - ServiceInfo si = mPM.getServiceInfo(className, flags); + ServiceInfo si = mPM.getServiceInfo(className, flags, UserId.myUserId()); if (si != null) { return si; } @@ -257,7 +258,7 @@ final class ApplicationPackageManager extends PackageManager { public ProviderInfo getProviderInfo(ComponentName className, int flags) throws NameNotFoundException { try { - ProviderInfo pi = mPM.getProviderInfo(className, flags); + ProviderInfo pi = mPM.getProviderInfo(className, flags, UserId.myUserId()); if (pi != null) { return pi; } @@ -422,6 +423,7 @@ final class ApplicationPackageManager extends PackageManager { @SuppressWarnings("unchecked") @Override public List<ApplicationInfo> getInstalledApplications(int flags) { + int userId = UserId.getUserId(Process.myUid()); try { final List<ApplicationInfo> applicationInfos = new ArrayList<ApplicationInfo>(); ApplicationInfo lastItem = null; @@ -429,7 +431,7 @@ final class ApplicationPackageManager extends PackageManager { do { final String lastKey = lastItem != null ? lastItem.packageName : null; - slice = mPM.getInstalledApplications(flags, lastKey); + slice = mPM.getInstalledApplications(flags, lastKey, userId); lastItem = slice.populateList(applicationInfos, ApplicationInfo.CREATOR); } while (!slice.isLastSlice()); @@ -445,7 +447,7 @@ final class ApplicationPackageManager extends PackageManager { return mPM.resolveIntent( intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), - flags); + flags, UserId.myUserId()); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } @@ -458,7 +460,8 @@ final class ApplicationPackageManager extends PackageManager { return mPM.queryIntentActivities( intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), - flags); + flags, + UserId.myUserId()); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } @@ -490,7 +493,7 @@ final class ApplicationPackageManager extends PackageManager { try { return mPM.queryIntentActivityOptions(caller, specifics, specificTypes, intent, intent.resolveTypeIfNeeded(resolver), - flags); + flags, UserId.myUserId()); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } @@ -502,7 +505,8 @@ final class ApplicationPackageManager extends PackageManager { return mPM.queryIntentReceivers( intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), - flags); + flags, + UserId.myUserId()); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } @@ -514,7 +518,8 @@ final class ApplicationPackageManager extends PackageManager { return mPM.resolveService( intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), - flags); + flags, + UserId.myUserId()); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } @@ -526,7 +531,8 @@ final class ApplicationPackageManager extends PackageManager { return mPM.queryIntentServices( intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), - flags); + flags, + UserId.myUserId()); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } @@ -536,7 +542,7 @@ final class ApplicationPackageManager extends PackageManager { public ProviderInfo resolveContentProvider(String name, int flags) { try { - return mPM.resolveContentProvider(name, flags); + return mPM.resolveContentProvider(name, flags, UserId.myUserId()); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } @@ -1026,7 +1032,7 @@ final class ApplicationPackageManager extends PackageManager { public void clearApplicationUserData(String packageName, IPackageDataObserver observer) { try { - mPM.clearApplicationUserData(packageName, observer); + mPM.clearApplicationUserData(packageName, observer, UserId.myUserId()); } catch (RemoteException e) { // Should never happen! } @@ -1139,7 +1145,7 @@ final class ApplicationPackageManager extends PackageManager { public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) { try { - mPM.setComponentEnabledSetting(componentName, newState, flags); + mPM.setComponentEnabledSetting(componentName, newState, flags, UserId.myUserId()); } catch (RemoteException e) { // Should never happen! } @@ -1148,7 +1154,7 @@ final class ApplicationPackageManager extends PackageManager { @Override public int getComponentEnabledSetting(ComponentName componentName) { try { - return mPM.getComponentEnabledSetting(componentName); + return mPM.getComponentEnabledSetting(componentName, UserId.myUserId()); } catch (RemoteException e) { // Should never happen! } @@ -1159,7 +1165,7 @@ final class ApplicationPackageManager extends PackageManager { public void setApplicationEnabledSetting(String packageName, int newState, int flags) { try { - mPM.setApplicationEnabledSetting(packageName, newState, flags); + mPM.setApplicationEnabledSetting(packageName, newState, flags, UserId.myUserId()); } catch (RemoteException e) { // Should never happen! } @@ -1168,7 +1174,7 @@ final class ApplicationPackageManager extends PackageManager { @Override public int getApplicationEnabledSetting(String packageName) { try { - return mPM.getApplicationEnabledSetting(packageName); + return mPM.getApplicationEnabledSetting(packageName, UserId.myUserId()); } catch (RemoteException e) { // Should never happen! } @@ -1209,6 +1215,18 @@ final class ApplicationPackageManager extends PackageManager { * @hide */ @Override + public UserInfo getUser(int userId) { + try { + return mPM.getUser(userId); + } catch (RemoteException re) { + return null; + } + } + + /** + * @hide + */ + @Override public boolean removeUser(int id) { try { return mPM.removeUser(id); @@ -1222,7 +1240,10 @@ final class ApplicationPackageManager extends PackageManager { */ @Override public void updateUserName(int id, String name) { - // TODO: + try { + mPM.updateUserName(id, name); + } catch (RemoteException re) { + } } /** diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 7043a73..d758eca 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -766,17 +766,18 @@ class ContextImpl extends Context { @Override public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory) { - File f = validateFilePath(name, true); - SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(f, factory); - setFilePermissionsFromMode(f.getPath(), mode, 0); - return db; + return openOrCreateDatabase(name, mode, factory, null); } @Override public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) { File f = validateFilePath(name, true); - SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(f.getPath(), factory, errorHandler); + int flags = SQLiteDatabase.CREATE_IF_NECESSARY; + if ((mode & MODE_ENABLE_WRITE_AHEAD_LOGGING) != 0) { + flags |= SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING; + } + SQLiteDatabase db = SQLiteDatabase.openDatabase(f.getPath(), factory, flags, errorHandler); setFilePermissionsFromMode(f.getPath(), mode, 0); return db; } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 31066b5..1d994d8 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -105,7 +105,7 @@ public interface IActivityManager extends IInterface { public List getServices(int maxNum, int flags) throws RemoteException; public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() throws RemoteException; - public void moveTaskToFront(int task, int flags) throws RemoteException; + public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException; public void moveTaskToBack(int task) throws RemoteException; public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException; public void moveTaskBackwards(int task) throws RemoteException; @@ -216,9 +216,10 @@ public interface IActivityManager extends IInterface { public void enterSafeMode() throws RemoteException; public void noteWakeupAlarm(IIntentSender sender) throws RemoteException; - + public boolean killPids(int[] pids, String reason, boolean secure) throws RemoteException; - + public boolean killProcessesBelowForeground(String reason) throws RemoteException; + // Special low-level communication with activity manager. public void startRunning(String pkg, String cls, String action, String data) throws RemoteException; @@ -573,4 +574,5 @@ public interface IActivityManager extends IInterface { int GET_CONTENT_PROVIDER_EXTERNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+140; int REMOVE_CONTENT_PROVIDER_EXTERNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+141; int GET_MY_MEMORY_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+142; + int KILL_PROCESSES_BELOW_FOREGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+143; } diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 16299de..e4f7950 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1381,6 +1381,7 @@ public class Instrumentation { } try { intent.setAllowFds(false); + intent.migrateExtraStreamToClipData(); int result = ActivityManagerNative.getDefault() .startActivity(whoThread, intent, intent.resolveTypeIfNeeded(who.getContentResolver()), @@ -1479,6 +1480,7 @@ public class Instrumentation { } try { intent.setAllowFds(false); + intent.migrateExtraStreamToClipData(); int result = ActivityManagerNative.getDefault() .startActivity(whoThread, intent, intent.resolveTypeIfNeeded(who.getContentResolver()), diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index de9470e..5340fbb 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -194,7 +194,7 @@ public final class LoadedApk { ApplicationInfo ai = null; try { ai = ActivityThread.getPackageManager().getApplicationInfo(packageName, - PackageManager.GET_SHARED_LIBRARY_FILES); + PackageManager.GET_SHARED_LIBRARY_FILES, UserId.myUserId()); } catch (RemoteException e) { throw new AssertionError(e); } @@ -351,7 +351,7 @@ public final class LoadedApk { IPackageManager pm = ActivityThread.getPackageManager(); android.content.pm.PackageInfo pi; try { - pi = pm.getPackageInfo(mPackageName, 0); + pi = pm.getPackageInfo(mPackageName, 0, UserId.myUserId()); } catch (RemoteException e) { throw new AssertionError(e); } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 5325af0..bbb6a4e 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -23,9 +23,11 @@ import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; +import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import android.util.IntProperty; import android.view.View; import android.widget.ProgressBar; import android.widget.RemoteViews; @@ -185,6 +187,13 @@ public class Notification implements Parcelable */ public RemoteViews contentView; + + /** + * The view that will represent this notification in the pop-up "intruder alert" dialog. + * @hide + */ + public RemoteViews intruderView; + /** * The bitmap that may escape the bounds of the panel and bar. */ @@ -418,6 +427,64 @@ public class Notification implements Parcelable private Bundle extras; /** + * Structure to encapsulate an "action", including title and icon, that can be attached to a Notification. + * @hide + */ + private static class Action implements Parcelable { + public int icon; + public CharSequence title; + public PendingIntent actionIntent; + @SuppressWarnings("unused") + public Action() { } + private Action(Parcel in) { + icon = in.readInt(); + title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + if (in.readInt() == 1) { + actionIntent = PendingIntent.CREATOR.createFromParcel(in); + } + } + public Action(int icon_, CharSequence title_, PendingIntent intent_) { + this.icon = icon_; + this.title = title_; + this.actionIntent = intent_; + } + @Override + public Action clone() { + return new Action( + this.icon, + this.title.toString(), + this.actionIntent // safe to alias + ); + } + @Override + public int describeContents() { + return 0; + } + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(icon); + TextUtils.writeToParcel(title, out, flags); + if (actionIntent != null) { + out.writeInt(1); + actionIntent.writeToParcel(out, flags); + } else { + out.writeInt(0); + } + } + public static final Parcelable.Creator<Action> CREATOR + = new Parcelable.Creator<Action>() { + public Action createFromParcel(Parcel in) { + return new Action(in); + } + public Action[] newArray(int size) { + return new Action[size]; + } + }; + } + + private Action[] actions; + + /** * Constructs a Notification object with default values. * You might want to consider using {@link Builder} instead. */ @@ -506,12 +573,17 @@ public class Notification implements Parcelable } priority = parcel.readInt(); - + kind = parcel.createStringArray(); // may set kind to null if (parcel.readInt() != 0) { extras = parcel.readBundle(); } + + actions = parcel.createTypedArray(Action.CREATOR); + if (parcel.readInt() != 0) { + intruderView = RemoteViews.CREATOR.createFromParcel(parcel); + } } @Override @@ -571,6 +643,14 @@ public class Notification implements Parcelable } + that.actions = new Action[this.actions.length]; + for(int i=0; i<this.actions.length; i++) { + that.actions[i] = this.actions[i].clone(); + } + if (this.intruderView != null) { + that.intruderView = this.intruderView.clone(); + } + return that; } @@ -658,6 +738,15 @@ public class Notification implements Parcelable } else { parcel.writeInt(0); } + + parcel.writeTypedArray(actions, 0); + + if (intruderView != null) { + parcel.writeInt(1); + intruderView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } } /** @@ -769,7 +858,14 @@ public class Notification implements Parcelable sb.append(this.kind[i]); } } - sb.append("])"); + sb.append("]"); + if (actions != null) { + sb.append(" "); + sb.append(actions.length); + sb.append(" action"); + if (actions.length > 1) sb.append("s"); + } + sb.append(")"); return sb.toString(); } @@ -821,6 +917,9 @@ public class Notification implements Parcelable private ArrayList<String> mKindList = new ArrayList<String>(1); private Bundle mExtras; private int mPriority; + private ArrayList<Action> mActions = new ArrayList<Action>(3); + private boolean mCanHasIntruder; + private boolean mIntruderActionsShowText; /** * Constructs a new Builder with the defaults: @@ -1203,6 +1302,51 @@ public class Notification implements Parcelable return this; } + /** + * Add an action to this notification. Actions are typically displayed by + * the system as a button adjacent to the notification content. + * + * @param icon Resource ID of a drawable that represents the action. + * @param title Text describing the action. + * @param intent PendingIntent to be fired when the action is invoked. + */ + public Builder addAction(int icon, CharSequence title, PendingIntent intent) { + mActions.add(new Action(icon, title, intent)); + return this; + } + + /** + * Specify whether this notification should pop up as an + * "intruder alert" (a small window that shares the screen with the + * current activity). This sort of notification is (as the name implies) + * very intrusive, so use it sparingly for notifications that require + * the user's attention. + * + * Notes: + * <ul> + * <li>Intruder alerts only show when the screen is on.</li> + * <li>Intruder alerts take precedence over fullScreenIntents.</li> + * </ul> + * + * @param intrude Whether to pop up an intruder alert (default false). + */ + public Builder setUsesIntruderAlert(boolean intrude) { + mCanHasIntruder = intrude; + return this; + } + + /** + * Control text on intruder alert action buttons. By default, action + * buttons in intruders do not show textual labels. + * + * @param showActionText Whether to show text labels beneath action + * icons (default false). + */ + public Builder setIntruderActionsShowText(boolean showActionText) { + mIntruderActionsShowText = showActionText; + return this; + } + private void setFlag(int mask, boolean value) { if (value) { mFlags |= mask; @@ -1284,6 +1428,45 @@ public class Notification implements Parcelable } } + private RemoteViews makeIntruderView(boolean showLabels) { + RemoteViews intruderView = new RemoteViews(mContext.getPackageName(), + R.layout.notification_intruder_content); + if (mLargeIcon != null) { + intruderView.setImageViewBitmap(R.id.icon, mLargeIcon); + intruderView.setViewVisibility(R.id.icon, View.VISIBLE); + } else if (mSmallIcon != 0) { + intruderView.setImageViewResource(R.id.icon, mSmallIcon); + intruderView.setViewVisibility(R.id.icon, View.VISIBLE); + } else { + intruderView.setViewVisibility(R.id.icon, View.GONE); + } + if (mContentTitle != null) { + intruderView.setTextViewText(R.id.title, mContentTitle); + } + if (mContentText != null) { + intruderView.setTextViewText(R.id.text, mContentText); + } + if (mActions.size() > 0) { + intruderView.setViewVisibility(R.id.actions, View.VISIBLE); + int N = mActions.size(); + if (N>3) N=3; + final int[] BUTTONS = { R.id.action0, R.id.action1, R.id.action2 }; + for (int i=0; i<N; i++) { + final Action action = mActions.get(i); + final int buttonId = BUTTONS[i]; + + intruderView.setViewVisibility(buttonId, View.VISIBLE); + intruderView.setTextViewText(buttonId, showLabels ? action.title : null); + intruderView.setTextViewCompoundDrawables(buttonId, 0, action.icon, 0, 0); + intruderView.setContentDescription(buttonId, action.title); + intruderView.setOnClickPendingIntent(buttonId, action.actionIntent); + } + } else { + intruderView.setViewVisibility(R.id.actions, View.GONE); + } + return intruderView; + } + /** * Combine all of the options that have been set and return a new {@link Notification} * object. @@ -1309,6 +1492,9 @@ public class Notification implements Parcelable n.ledOffMS = mLedOffMs; n.defaults = mDefaults; n.flags = mFlags; + if (mCanHasIntruder) { + n.intruderView = makeIntruderView(mIntruderActionsShowText); + } if (mLedOnMs != 0 && mLedOffMs != 0) { n.flags |= FLAG_SHOW_LIGHTS; } @@ -1323,6 +1509,10 @@ public class Notification implements Parcelable } n.priority = mPriority; n.extras = mExtras != null ? new Bundle(mExtras) : null; + if (mActions.size() > 0) { + n.actions = new Action[mActions.size()]; + mActions.toArray(n.actions); + } return n; } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 741a6e9..2902504 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -99,6 +99,16 @@ public abstract class Context { public static final int MODE_MULTI_PROCESS = 0x0004; /** + * Database open flag: when set, the database is opened with write-ahead + * logging enabled by default. + * + * @see #openOrCreateDatabase(String, int, CursorFactory) + * @see #openOrCreateDatabase(String, int, CursorFactory, DatabaseErrorHandler) + * @see SQLiteDatabase#enableWriteAheadLogging + */ + public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 0x0008; + + /** * Flag for {@link #bindService}: automatically create the service as long * as the binding exists. Note that while this will create the service, * its {@link android.app.Service#onStartCommand} @@ -691,6 +701,7 @@ public abstract class Context { * @param mode Operating mode. Use 0 or {@link #MODE_PRIVATE} for the * default operation, {@link #MODE_WORLD_READABLE} * and {@link #MODE_WORLD_WRITEABLE} to control permissions. + * Use {@link #MODE_ENABLE_WRITE_AHEAD_LOGGING} to enable write-ahead logging by default. * @param factory An optional factory class that is called to instantiate a * cursor when query is called. * @@ -700,6 +711,7 @@ public abstract class Context { * @see #MODE_PRIVATE * @see #MODE_WORLD_READABLE * @see #MODE_WORLD_WRITEABLE + * @see #MODE_ENABLE_WRITE_AHEAD_LOGGING * @see #deleteDatabase */ public abstract SQLiteDatabase openOrCreateDatabase(String name, @@ -716,6 +728,7 @@ public abstract class Context { * @param mode Operating mode. Use 0 or {@link #MODE_PRIVATE} for the * default operation, {@link #MODE_WORLD_READABLE} * and {@link #MODE_WORLD_WRITEABLE} to control permissions. + * Use {@link #MODE_ENABLE_WRITE_AHEAD_LOGGING} to enable write-ahead logging by default. * @param factory An optional factory class that is called to instantiate a * cursor when query is called. * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database @@ -726,6 +739,7 @@ public abstract class Context { * @see #MODE_PRIVATE * @see #MODE_WORLD_READABLE * @see #MODE_WORLD_WRITEABLE + * @see #MODE_ENABLE_WRITE_AHEAD_LOGGING * @see #deleteDatabase */ public abstract SQLiteDatabase openOrCreateDatabase(String name, diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 6cf5b43..2a9f1af 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2141,6 +2141,30 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_PRE_BOOT_COMPLETED = "android.intent.action.PRE_BOOT_COMPLETED"; + /** + * Broadcast sent to the system when a user is added. Carries an extra EXTRA_USERID that has the + * userid of the new user. + * @hide + */ + public static final String ACTION_USER_ADDED = + "android.intent.action.USER_ADDED"; + + /** + * Broadcast sent to the system when a user is removed. Carries an extra EXTRA_USERID that has + * the userid of the user. + * @hide + */ + public static final String ACTION_USER_REMOVED = + "android.intent.action.USER_REMOVED"; + + /** + * Broadcast sent to the system when the user switches. Carries an extra EXTRA_USERID that has + * the userid of the user to become the current one. + * @hide + */ + public static final String ACTION_USER_SWITCHED = + "android.intent.action.USER_SWITCHED"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent categories (see addCategory()). @@ -2682,6 +2706,13 @@ public class Intent implements Parcelable, Cloneable { public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY"; + /** + * The userid carried with broadcast intents related to addition, removal and switching of users + * - {@link #ACTION_USER_ADDED}, {@link #ACTION_USER_REMOVED} and {@link #ACTION_USER_SWITCHED}. + * @hide + */ + public static final String EXTRA_USERID = + "android.intent.extra.user_id"; // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Intent flags (see mFlags variable). @@ -6467,4 +6498,56 @@ public class Intent implements Parcelable, Cloneable { } return type; } + + /** + * Migrate any {@link #EXTRA_STREAM} in {@link #ACTION_SEND} and + * {@link #ACTION_SEND_MULTIPLE} to {@link ClipData}. + * + * @hide + */ + public void migrateExtraStreamToClipData() { + // Refuse to touch if extras already parcelled + if (mExtras != null && mExtras.isParcelled()) return; + + // Bail when someone already gave us ClipData + if (getClipData() != null) return; + + final String action = getAction(); + if (ACTION_SEND.equals(action)) { + final Uri stream; + try { + stream = getParcelableExtra(EXTRA_STREAM); + } catch (ClassCastException e) { + return; + } + if (stream != null) { + final ClipData clipData = new ClipData( + null, new String[] { getType() }, new ClipData.Item(stream)); + + setClipData(clipData); + addFlags(FLAG_GRANT_READ_URI_PERMISSION); + } + + } else if (ACTION_SEND_MULTIPLE.equals(action)) { + final ArrayList<Uri> streams; + try { + streams = getParcelableArrayListExtra(EXTRA_STREAM); + } catch (ClassCastException e) { + return; + } + if (streams != null && streams.size() > 0) { + final Uri firstStream = streams.get(0); + final ClipData clipData = new ClipData( + null, new String[] { getType() }, new ClipData.Item(firstStream)); + + final int size = streams.size(); + for (int i = 1; i < size; i++) { + clipData.addItem(new ClipData.Item(streams.get(i))); + } + + setClipData(clipData); + addFlags(FLAG_GRANT_READ_URI_PERMISSION); + } + } + } } diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index b7dfe92..06dfe90 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -326,6 +326,13 @@ public class SyncManager implements OnAccountsUpdateListener { } }; + private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + onUserRemoved(intent); + } + }; + private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM"; private final SyncHandler mSyncHandler; @@ -420,6 +427,10 @@ public class SyncManager implements OnAccountsUpdateListener { intentFilter.setPriority(100); context.registerReceiver(mShutdownIntentReceiver, intentFilter); + intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_USER_REMOVED); + mContext.registerReceiver(mUserIntentReceiver, intentFilter); + if (!factoryTest) { mNotificationMgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); @@ -905,6 +916,18 @@ public class SyncManager implements OnAccountsUpdateListener { } } + private void onUserRemoved(Intent intent) { + int userId = intent.getIntExtra(Intent.EXTRA_USERID, -1); + if (userId == -1) return; + + // Clean up the storage engine database + mSyncStorageEngine.doDatabaseCleanup(new Account[0], userId); + onAccountsUpdated(null); + synchronized (mSyncQueue) { + mSyncQueue.removeUser(userId); + } + } + /** * @hide */ diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java index 06da6fa..c18c86b 100644 --- a/core/java/android/content/SyncQueue.java +++ b/core/java/android/content/SyncQueue.java @@ -117,6 +117,19 @@ public class SyncQueue { return true; } + public void removeUser(int userId) { + ArrayList<SyncOperation> opsToRemove = new ArrayList<SyncOperation>(); + for (SyncOperation op : mOperationsMap.values()) { + if (op.userId == userId) { + opsToRemove.add(op); + } + } + + for (SyncOperation op : opsToRemove) { + remove(op); + } + } + /** * Remove the specified operation if it is in the queue. * @param operation the operation to remove diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 9bd1940..56fd5f8 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -49,8 +49,8 @@ import android.content.IntentSender; * {@hide} */ interface IPackageManager { - PackageInfo getPackageInfo(String packageName, int flags); - int getPackageUid(String packageName); + PackageInfo getPackageInfo(String packageName, int flags, int userId); + int getPackageUid(String packageName, int userId); int[] getPackageGids(String packageName); String[] currentToCanonicalPackageNames(in String[] names); @@ -64,15 +64,15 @@ interface IPackageManager { List<PermissionGroupInfo> getAllPermissionGroups(int flags); - ApplicationInfo getApplicationInfo(String packageName, int flags); + ApplicationInfo getApplicationInfo(String packageName, int flags ,int userId); - ActivityInfo getActivityInfo(in ComponentName className, int flags); + ActivityInfo getActivityInfo(in ComponentName className, int flags, int userId); - ActivityInfo getReceiverInfo(in ComponentName className, int flags); + ActivityInfo getReceiverInfo(in ComponentName className, int flags, int userId); - ServiceInfo getServiceInfo(in ComponentName className, int flags); + ServiceInfo getServiceInfo(in ComponentName className, int flags, int userId); - ProviderInfo getProviderInfo(in ComponentName className, int flags); + ProviderInfo getProviderInfo(in ComponentName className, int flags, int userId); int checkPermission(String permName, String pkgName); @@ -98,24 +98,24 @@ interface IPackageManager { int getUidForSharedUser(String sharedUserName); - ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags); + ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags, int userId); List<ResolveInfo> queryIntentActivities(in Intent intent, - String resolvedType, int flags); + String resolvedType, int flags, int userId); List<ResolveInfo> queryIntentActivityOptions( in ComponentName caller, in Intent[] specifics, in String[] specificTypes, in Intent intent, - String resolvedType, int flags); + String resolvedType, int flags, int userId); List<ResolveInfo> queryIntentReceivers(in Intent intent, - String resolvedType, int flags); + String resolvedType, int flags, int userId); ResolveInfo resolveService(in Intent intent, - String resolvedType, int flags); + String resolvedType, int flags, int userId); List<ResolveInfo> queryIntentServices(in Intent intent, - String resolvedType, int flags); + String resolvedType, int flags, int userId); /** * This implements getInstalledPackages via a "last returned row" @@ -131,7 +131,7 @@ interface IPackageManager { * limit that kicks in when flags are included that bloat up the data * returned. */ - ParceledListSlice getInstalledApplications(int flags, in String lastRead); + ParceledListSlice getInstalledApplications(int flags, in String lastRead, int userId); /** * Retrieve all applications that are marked as persistent. @@ -141,7 +141,7 @@ interface IPackageManager { */ List<ApplicationInfo> getPersistentApplications(int flags); - ProviderInfo resolveContentProvider(String name, int flags); + ProviderInfo resolveContentProvider(String name, int flags, int userId); /** * Retrieve sync information for all content providers. @@ -212,28 +212,28 @@ interface IPackageManager { * As per {@link android.content.pm.PackageManager#setComponentEnabledSetting}. */ void setComponentEnabledSetting(in ComponentName componentName, - in int newState, in int flags); + in int newState, in int flags, int userId); /** * As per {@link android.content.pm.PackageManager#getComponentEnabledSetting}. */ - int getComponentEnabledSetting(in ComponentName componentName); + int getComponentEnabledSetting(in ComponentName componentName, int userId); /** * As per {@link android.content.pm.PackageManager#setApplicationEnabledSetting}. */ - void setApplicationEnabledSetting(in String packageName, in int newState, int flags); + void setApplicationEnabledSetting(in String packageName, in int newState, int flags, int userId); /** * As per {@link android.content.pm.PackageManager#getApplicationEnabledSetting}. */ - int getApplicationEnabledSetting(in String packageName); + int getApplicationEnabledSetting(in String packageName, int userId); /** * Set whether the given package should be considered stopped, making * it not visible to implicit intents that filter out stopped packages. */ - void setPackageStoppedState(String packageName, boolean stopped); + void setPackageStoppedState(String packageName, boolean stopped, int userId); /** * Free storage by deleting LRU sorted list of cache files across @@ -296,7 +296,7 @@ interface IPackageManager { * files need to be deleted * @param observer a callback used to notify when the operation is completed. */ - void clearApplicationUserData(in String packageName, IPackageDataObserver observer); + void clearApplicationUserData(in String packageName, IPackageDataObserver observer, int userId); /** * Get package statistics including the code, data and cache size for @@ -358,6 +358,7 @@ interface IPackageManager { UserInfo createUser(in String name, int flags); boolean removeUser(int userId); + void updateUserName(int userId, String name); void installPackageWithVerification(in Uri packageURI, in IPackageInstallObserver observer, int flags, in String installerPackageName, in Uri verificationURI, @@ -370,6 +371,7 @@ interface IPackageManager { boolean isFirstBoot(); List<UserInfo> getUsers(); + UserInfo getUser(int userId); void setPermissionEnforcement(String permission, int enforcement); int getPermissionEnforcement(String permission); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 55426b8..b06b4a5 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2153,7 +2153,8 @@ public abstract class PackageManager { if ((flags & GET_SIGNATURES) != 0) { packageParser.collectCertificates(pkg, 0); } - return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null); + return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, false, + COMPONENT_ENABLED_STATE_DEFAULT); } /** @@ -2637,10 +2638,17 @@ public abstract class PackageManager { public abstract void updateUserFlags(int id, int flags); /** - * Returns the device identity that verifiers can use to associate their - * scheme to a particular device. This should not be used by anything other - * than a package verifier. - * + * Returns the details for the user specified by userId. + * @param userId the user id of the user + * @return UserInfo for the specified user, or null if no such user exists. + * @hide + */ + public abstract UserInfo getUser(int userId); + + /** + * Returns the device identity that verifiers can use to associate their scheme to a particular + * device. This should not be used by anything other than a package verifier. + * * @return identity that uniquely identifies current device * @hide */ diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 207f077..eb8536f 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -230,6 +230,15 @@ public class PackageParser { return name.endsWith(".apk"); } + public static PackageInfo generatePackageInfo(PackageParser.Package p, + int gids[], int flags, long firstInstallTime, long lastUpdateTime, + HashSet<String> grantedPermissions) { + + return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime, + grantedPermissions, false, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, + UserId.getCallingUserId()); + } + /** * Generate and return the {@link PackageInfo} for a parsed package. * @@ -238,9 +247,15 @@ public class PackageParser { */ public static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, - HashSet<String> grantedPermissions) { + HashSet<String> grantedPermissions, boolean stopped, int enabledState) { - final int userId = Binder.getOrigCallingUser(); + return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime, + grantedPermissions, stopped, enabledState, UserId.getCallingUserId()); + } + + public static PackageInfo generatePackageInfo(PackageParser.Package p, + int gids[], int flags, long firstInstallTime, long lastUpdateTime, + HashSet<String> grantedPermissions, boolean stopped, int enabledState, int userId) { PackageInfo pi = new PackageInfo(); pi.packageName = p.packageName; @@ -248,7 +263,7 @@ public class PackageParser { pi.versionName = p.mVersionName; pi.sharedUserId = p.mSharedUserId; pi.sharedUserLabel = p.mSharedUserLabel; - pi.applicationInfo = generateApplicationInfo(p, flags); + pi.applicationInfo = generateApplicationInfo(p, flags, stopped, enabledState, userId); pi.installLocation = p.installLocation; pi.firstInstallTime = firstInstallTime; pi.lastUpdateTime = lastUpdateTime; @@ -284,7 +299,7 @@ public class PackageParser { if (activity.info.enabled || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { pi.activities[j++] = generateActivityInfo(p.activities.get(i), flags, - userId); + stopped, enabledState, userId); } } } @@ -305,7 +320,8 @@ public class PackageParser { final Activity activity = p.receivers.get(i); if (activity.info.enabled || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { - pi.receivers[j++] = generateActivityInfo(p.receivers.get(i), flags, userId); + pi.receivers[j++] = generateActivityInfo(p.receivers.get(i), flags, + stopped, enabledState, userId); } } } @@ -326,7 +342,8 @@ public class PackageParser { final Service service = p.services.get(i); if (service.info.enabled || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { - pi.services[j++] = generateServiceInfo(p.services.get(i), flags, userId); + pi.services[j++] = generateServiceInfo(p.services.get(i), flags, stopped, + enabledState, userId); } } } @@ -347,7 +364,8 @@ public class PackageParser { final Provider provider = p.providers.get(i); if (provider.info.enabled || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { - pi.providers[j++] = generateProviderInfo(p.providers.get(i), flags, userId); + pi.providers[j++] = generateProviderInfo(p.providers.get(i), flags, stopped, + enabledState, userId); } } } @@ -3062,11 +3080,11 @@ public class PackageParser { // For use by package manager to keep track of where it has done dexopt. public boolean mDidDexOpt; - // User set enabled state. - public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; - - // Whether the package has been stopped. - public boolean mSetStopped = false; + // // User set enabled state. + // public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; + // + // // Whether the package has been stopped. + // public boolean mSetStopped = false; // Additional data supplied by callers. public Object mExtras; @@ -3331,9 +3349,9 @@ public class PackageParser { } } - private static boolean copyNeeded(int flags, Package p, Bundle metaData) { - if (p.mSetEnabled != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { - boolean enabled = p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; + private static boolean copyNeeded(int flags, Package p, int enabledState, Bundle metaData) { + if (enabledState != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { + boolean enabled = enabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; if (p.applicationInfo.enabled != enabled) { return true; } @@ -3349,23 +3367,32 @@ public class PackageParser { return false; } - public static ApplicationInfo generateApplicationInfo(Package p, int flags) { - return generateApplicationInfo(p, flags, UserId.getUserId(Binder.getCallingUid())); + public static ApplicationInfo generateApplicationInfo(Package p, int flags, boolean stopped, + int enabledState) { + return generateApplicationInfo(p, flags, stopped, enabledState, UserId.getCallingUserId()); } - public static ApplicationInfo generateApplicationInfo(Package p, int flags, int userId) { + public static ApplicationInfo generateApplicationInfo(Package p, int flags, + boolean stopped, int enabledState, int userId) { if (p == null) return null; - if (!copyNeeded(flags, p, null) && userId == 0) { + if (!copyNeeded(flags, p, enabledState, null) && userId == 0) { // CompatibilityMode is global state. It's safe to modify the instance // of the package. if (!sCompatibilityModeEnabled) { p.applicationInfo.disableCompatibilityMode(); } - if (p.mSetStopped) { + if (stopped) { p.applicationInfo.flags |= ApplicationInfo.FLAG_STOPPED; } else { p.applicationInfo.flags &= ~ApplicationInfo.FLAG_STOPPED; } + if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { + p.applicationInfo.enabled = true; + } else if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED + || enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { + p.applicationInfo.enabled = false; + } + p.applicationInfo.enabledSetting = enabledState; return p.applicationInfo; } @@ -3384,18 +3411,18 @@ public class PackageParser { if (!sCompatibilityModeEnabled) { ai.disableCompatibilityMode(); } - if (p.mSetStopped) { + if (stopped) { p.applicationInfo.flags |= ApplicationInfo.FLAG_STOPPED; } else { p.applicationInfo.flags &= ~ApplicationInfo.FLAG_STOPPED; } - if (p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { + if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { ai.enabled = true; - } else if (p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED - || p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { + } else if (enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED + || enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { ai.enabled = false; } - ai.enabledSetting = p.mSetEnabled; + ai.enabledSetting = enabledState; return ai; } @@ -3442,15 +3469,16 @@ public class PackageParser { } } - public static final ActivityInfo generateActivityInfo(Activity a, int flags, int userId) { + public static final ActivityInfo generateActivityInfo(Activity a, int flags, boolean stopped, + int enabledState, int userId) { if (a == null) return null; - if (!copyNeeded(flags, a.owner, a.metaData) && userId == 0) { + if (!copyNeeded(flags, a.owner, enabledState, a.metaData) && userId == 0) { return a.info; } // Make shallow copies so we can store the metadata safely ActivityInfo ai = new ActivityInfo(a.info); ai.metaData = a.metaData; - ai.applicationInfo = generateApplicationInfo(a.owner, flags, userId); + ai.applicationInfo = generateApplicationInfo(a.owner, flags, stopped, enabledState, userId); return ai; } @@ -3475,16 +3503,17 @@ public class PackageParser { } } - public static final ServiceInfo generateServiceInfo(Service s, int flags, int userId) { + public static final ServiceInfo generateServiceInfo(Service s, int flags, boolean stopped, + int enabledState, int userId) { if (s == null) return null; - if (!copyNeeded(flags, s.owner, s.metaData) + if (!copyNeeded(flags, s.owner, enabledState, s.metaData) && userId == UserId.getUserId(s.info.applicationInfo.uid)) { return s.info; } // Make shallow copies so we can store the metadata safely ServiceInfo si = new ServiceInfo(s.info); si.metaData = s.metaData; - si.applicationInfo = generateApplicationInfo(s.owner, flags, userId); + si.applicationInfo = generateApplicationInfo(s.owner, flags, stopped, enabledState, userId); return si; } @@ -3517,9 +3546,10 @@ public class PackageParser { } } - public static final ProviderInfo generateProviderInfo(Provider p, int flags, int userId) { + public static final ProviderInfo generateProviderInfo(Provider p, int flags, boolean stopped, + int enabledState, int userId) { if (p == null) return null; - if (!copyNeeded(flags, p.owner, p.metaData) + if (!copyNeeded(flags, p.owner, enabledState, p.metaData) && ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) != 0 || p.info.uriPermissionPatterns == null) && userId == 0) { @@ -3531,7 +3561,7 @@ public class PackageParser { if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) { pi.uriPermissionPatterns = null; } - pi.applicationInfo = generateApplicationInfo(p.owner, flags, userId); + pi.applicationInfo = generateApplicationInfo(p.owner, flags, stopped, enabledState, userId); return pi; } diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index e2c222b..254f652 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -211,8 +211,8 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME); setPageSize(); - setSyncModeFromConfiguration(); - setJournalModeFromConfiguration(); + setForeignKeyModeFromConfiguration(); + setWalModeFromConfiguration(); setJournalSizeLimit(); setAutoCheckpointInterval(); setLocaleFromConfiguration(); @@ -268,28 +268,79 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } } - private void setSyncModeFromConfiguration() { - if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) { - final String newValue = mConfiguration.syncMode; - String value = executeForString("PRAGMA synchronous", null, null); - if (!value.equalsIgnoreCase(newValue)) { - execute("PRAGMA synchronous=" + newValue, null, null); + private void setForeignKeyModeFromConfiguration() { + if (!mIsReadOnlyConnection) { + final long newValue = mConfiguration.foreignKeyConstraintsEnabled ? 1 : 0; + long value = executeForLong("PRAGMA foreign_keys", null, null); + if (value != newValue) { + execute("PRAGMA foreign_keys=" + newValue, null, null); } } } - private void setJournalModeFromConfiguration() { + private void setWalModeFromConfiguration() { if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) { - final String newValue = mConfiguration.journalMode; - String value = executeForString("PRAGMA journal_mode", null, null); - if (!value.equalsIgnoreCase(newValue)) { - value = executeForString("PRAGMA journal_mode=" + newValue, null, null); - if (!value.equalsIgnoreCase(newValue)) { - Log.e(TAG, "setting journal_mode to " + newValue - + " failed for db: " + mConfiguration.label - + " (on pragma set journal_mode, sqlite returned:" + value); + if ((mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) { + setJournalMode("WAL"); + setSyncMode(SQLiteGlobal.getWALSyncMode()); + } else { + setJournalMode(SQLiteGlobal.getDefaultJournalMode()); + setSyncMode(SQLiteGlobal.getDefaultSyncMode()); + } + } + } + + private void setSyncMode(String newValue) { + String value = executeForString("PRAGMA synchronous", null, null); + if (!canonicalizeSyncMode(value).equalsIgnoreCase( + canonicalizeSyncMode(newValue))) { + execute("PRAGMA synchronous=" + newValue, null, null); + } + } + + private static String canonicalizeSyncMode(String value) { + if (value.equals("0")) { + return "OFF"; + } else if (value.equals("1")) { + return "NORMAL"; + } else if (value.equals("2")) { + return "FULL"; + } + return value; + } + + private void setJournalMode(String newValue) { + String value = executeForString("PRAGMA journal_mode", null, null); + if (!value.equalsIgnoreCase(newValue)) { + try { + String result = executeForString("PRAGMA journal_mode=" + newValue, null, null); + if (result.equalsIgnoreCase(newValue)) { + return; } + // PRAGMA journal_mode silently fails and returns the original journal + // mode in some cases if the journal mode could not be changed. + } catch (SQLiteDatabaseLockedException ex) { + // This error (SQLITE_BUSY) occurs if one connection has the database + // open in WAL mode and another tries to change it to non-WAL. } + // Because we always disable WAL mode when a database is first opened + // (even if we intend to re-enable it), we can encounter problems if + // there is another open connection to the database somewhere. + // This can happen for a variety of reasons such as an application opening + // the same database in multiple processes at the same time or if there is a + // crashing content provider service that the ActivityManager has + // removed from its registry but whose process hasn't quite died yet + // by the time it is restarted in a new process. + // + // If we don't change the journal mode, nothing really bad happens. + // In the worst case, an application that enables WAL might not actually + // get it, although it can still use connection pooling. + Log.w(TAG, "Could not change the database journal mode of '" + + mConfiguration.label + "' from '" + value + "' to '" + newValue + + "' because the database is locked. This usually means that " + + "there are other open connections to the database which prevents " + + "the database from enabling or disabling write-ahead logging mode. " + + "Proceeding without changing the journal mode."); } } @@ -349,10 +400,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } // Remember what changed. - boolean syncModeChanged = !configuration.syncMode.equalsIgnoreCase( - mConfiguration.syncMode); - boolean journalModeChanged = !configuration.journalMode.equalsIgnoreCase( - mConfiguration.journalMode); + boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled + != mConfiguration.foreignKeyConstraintsEnabled; + boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags) + & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0; boolean localeChanged = !configuration.locale.equals(mConfiguration.locale); // Update configuration parameters. @@ -361,14 +412,14 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen // Update prepared statement cache size. mPreparedStatementCache.resize(configuration.maxSqlCacheSize); - // Update sync mode. - if (syncModeChanged) { - setSyncModeFromConfiguration(); + // Update foreign key mode. + if (foreignKeyModeChanged) { + setForeignKeyModeFromConfiguration(); } - // Update journal mode. - if (journalModeChanged) { - setJournalModeFromConfiguration(); + // Update WAL. + if (walModeChanged) { + setWalModeFromConfiguration(); } // Update locale. diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java index 3562e89..5c8e38b 100644 --- a/core/java/android/database/sqlite/SQLiteConnectionPool.java +++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java @@ -81,6 +81,7 @@ public final class SQLiteConnectionPool implements Closeable { private final Object mLock = new Object(); private final AtomicBoolean mConnectionLeaked = new AtomicBoolean(); private final SQLiteDatabaseConfiguration mConfiguration; + private int mMaxConnectionPoolSize; private boolean mIsOpen; private int mNextConnectionId; @@ -146,6 +147,7 @@ public final class SQLiteConnectionPool implements Closeable { private SQLiteConnectionPool(SQLiteDatabaseConfiguration configuration) { mConfiguration = new SQLiteDatabaseConfiguration(configuration); + setMaxConnectionPoolSizeLocked(); } @Override @@ -257,7 +259,46 @@ public final class SQLiteConnectionPool implements Closeable { synchronized (mLock) { throwIfClosedLocked(); + boolean walModeChanged = ((configuration.openFlags ^ mConfiguration.openFlags) + & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0; + if (walModeChanged) { + // WAL mode can only be changed if there are no acquired connections + // because we need to close all but the primary connection first. + if (!mAcquiredConnections.isEmpty()) { + throw new IllegalStateException("Write Ahead Logging (WAL) mode cannot " + + "be enabled or disabled while there are transactions in " + + "progress. Finish all transactions and release all active " + + "database connections first."); + } + + // Close all non-primary connections. This should happen immediately + // because none of them are in use. + closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked(); + assert mAvailableNonPrimaryConnections.isEmpty(); + } + + boolean foreignKeyModeChanged = configuration.foreignKeyConstraintsEnabled + != mConfiguration.foreignKeyConstraintsEnabled; + if (foreignKeyModeChanged) { + // Foreign key constraints can only be changed if there are no transactions + // in progress. To make this clear, we throw an exception if there are + // any acquired connections. + if (!mAcquiredConnections.isEmpty()) { + throw new IllegalStateException("Foreign Key Constraints cannot " + + "be enabled or disabled while there are transactions in " + + "progress. Finish all transactions and release all active " + + "database connections first."); + } + } + if (mConfiguration.openFlags != configuration.openFlags) { + // If we are changing open flags and WAL mode at the same time, then + // we have no choice but to close the primary connection beforehand + // because there can only be one connection open when we change WAL mode. + if (walModeChanged) { + closeAvailableConnectionsAndLogExceptionsLocked(); + } + // Try to reopen the primary connection using the new open flags then // close and discard all existing connections. // This might throw if the database is corrupt or cannot be opened in @@ -270,9 +311,11 @@ public final class SQLiteConnectionPool implements Closeable { mAvailablePrimaryConnection = newPrimaryConnection; mConfiguration.updateParametersFrom(configuration); + setMaxConnectionPoolSizeLocked(); } else { // Reconfigure the database connections in place. mConfiguration.updateParametersFrom(configuration); + setMaxConnectionPoolSizeLocked(); closeExcessConnectionsAndLogExceptionsLocked(); reconfigureAllConnectionsLocked(); @@ -334,8 +377,7 @@ public final class SQLiteConnectionPool implements Closeable { mAvailablePrimaryConnection = connection; } wakeConnectionWaitersLocked(); - } else if (mAvailableNonPrimaryConnections.size() >= - mConfiguration.maxConnectionPoolSize - 1) { + } else if (mAvailableNonPrimaryConnections.size() >= mMaxConnectionPoolSize - 1) { closeConnectionAndLogExceptionsLocked(connection); } else { if (recycleConnectionLocked(connection, status)) { @@ -453,11 +495,7 @@ public final class SQLiteConnectionPool implements Closeable { // Can't throw. private void closeAvailableConnectionsAndLogExceptionsLocked() { - final int count = mAvailableNonPrimaryConnections.size(); - for (int i = 0; i < count; i++) { - closeConnectionAndLogExceptionsLocked(mAvailableNonPrimaryConnections.get(i)); - } - mAvailableNonPrimaryConnections.clear(); + closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked(); if (mAvailablePrimaryConnection != null) { closeConnectionAndLogExceptionsLocked(mAvailablePrimaryConnection); @@ -466,9 +504,18 @@ public final class SQLiteConnectionPool implements Closeable { } // Can't throw. + private void closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked() { + final int count = mAvailableNonPrimaryConnections.size(); + for (int i = 0; i < count; i++) { + closeConnectionAndLogExceptionsLocked(mAvailableNonPrimaryConnections.get(i)); + } + mAvailableNonPrimaryConnections.clear(); + } + + // Can't throw. private void closeExcessConnectionsAndLogExceptionsLocked() { int availableCount = mAvailableNonPrimaryConnections.size(); - while (availableCount-- > mConfiguration.maxConnectionPoolSize - 1) { + while (availableCount-- > mMaxConnectionPoolSize - 1) { SQLiteConnection connection = mAvailableNonPrimaryConnections.remove(availableCount); closeConnectionAndLogExceptionsLocked(connection); @@ -843,7 +890,7 @@ public final class SQLiteConnectionPool implements Closeable { if (mAvailablePrimaryConnection != null) { openConnections += 1; } - if (openConnections >= mConfiguration.maxConnectionPoolSize) { + if (openConnections >= mMaxConnectionPoolSize) { return null; } connection = openConnectionLocked(mConfiguration, @@ -895,6 +942,18 @@ public final class SQLiteConnectionPool implements Closeable { return (connectionFlags & CONNECTION_FLAG_INTERACTIVE) != 0 ? 1 : 0; } + private void setMaxConnectionPoolSizeLocked() { + if ((mConfiguration.openFlags & SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING) != 0) { + mMaxConnectionPoolSize = SQLiteGlobal.getWALConnectionPoolSize(); + } else { + // TODO: We don't actually need to restrict the connection pool size to 1 + // for non-WAL databases. There might be reasons to use connection pooling + // with other journal modes. For now, enabling connection pooling and + // using WAL are the same thing in the API. + mMaxConnectionPoolSize = 1; + } + } + private void throwIfClosedLocked() { if (!mIsOpen) { throw new IllegalStateException("Cannot perform this operation " @@ -941,7 +1000,7 @@ public final class SQLiteConnectionPool implements Closeable { synchronized (mLock) { printer.println("Connection pool for " + mConfiguration.path + ":"); printer.println(" Open: " + mIsOpen); - printer.println(" Max connections: " + mConfiguration.maxConnectionPoolSize); + printer.println(" Max connections: " + mMaxConnectionPoolSize); printer.println(" Available primary connection:"); if (mAvailablePrimaryConnection != null) { diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index bf32ea7..7bd0c8d 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -127,10 +127,6 @@ public final class SQLiteDatabase extends SQLiteClosable { // INVARIANT: Guarded by mLock. private boolean mHasAttachedDbsLocked; - // True if the database is in WAL mode. - // INVARIANT: Guarded by mLock. - private boolean mIsWALEnabledLocked; - /** * When a constraint violation occurs, an immediate ROLLBACK occurs, * thus ending the current transaction, and the command aborts with a @@ -236,6 +232,18 @@ public final class SQLiteDatabase extends SQLiteClosable { public static final int CREATE_IF_NECESSARY = 0x10000000; // update native code if changing /** + * Open flag: Flag for {@link #openDatabase} to open the database file with + * write-ahead logging enabled by default. Using this flag is more efficient + * than calling {@link #enableWriteAheadLogging}. + * + * Write-ahead logging cannot be used with read-only databases so the value of + * this flag is ignored if the database is opened read-only. + * + * @see #enableWriteAheadLogging + */ + public static final int ENABLE_WRITE_AHEAD_LOGGING = 0x20000000; + + /** * Absolute max value that can be set by {@link #setMaxSqlCacheSize(int)}. * * Each prepared-statement is between 1K - 6K, depending on the complexity of the @@ -658,7 +666,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * @throws SQLiteException if the database cannot be opened */ public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags) { - return openDatabase(path, factory, flags, new DefaultDatabaseErrorHandler()); + return openDatabase(path, factory, flags, null); } /** @@ -698,7 +706,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * Equivalent to openDatabase(path, factory, CREATE_IF_NECESSARY). */ public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory) { - return openDatabase(path, factory, CREATE_IF_NECESSARY); + return openDatabase(path, factory, CREATE_IF_NECESSARY, null); } /** @@ -834,8 +842,14 @@ public final class SQLiteDatabase extends SQLiteClosable { synchronized (mLock) { throwIfNotOpenLocked(); + mConfigurationLocked.customFunctions.add(wrapper); - mConnectionPoolLocked.reconfigure(mConfigurationLocked); + try { + mConnectionPoolLocked.reconfigure(mConfigurationLocked); + } catch (RuntimeException ex) { + mConfigurationLocked.customFunctions.remove(wrapper); + throw ex; + } } } @@ -1733,8 +1747,15 @@ public final class SQLiteDatabase extends SQLiteClosable { synchronized (mLock) { throwIfNotOpenLocked(); + + final Locale oldLocale = mConfigurationLocked.locale; mConfigurationLocked.locale = locale; - mConnectionPoolLocked.reconfigure(mConfigurationLocked); + try { + mConnectionPoolLocked.reconfigure(mConfigurationLocked); + } catch (RuntimeException ex) { + mConfigurationLocked.locale = oldLocale; + throw ex; + } } } @@ -1759,58 +1780,144 @@ public final class SQLiteDatabase extends SQLiteClosable { synchronized (mLock) { throwIfNotOpenLocked(); + + final int oldMaxSqlCacheSize = mConfigurationLocked.maxSqlCacheSize; mConfigurationLocked.maxSqlCacheSize = cacheSize; - mConnectionPoolLocked.reconfigure(mConfigurationLocked); + try { + mConnectionPoolLocked.reconfigure(mConfigurationLocked); + } catch (RuntimeException ex) { + mConfigurationLocked.maxSqlCacheSize = oldMaxSqlCacheSize; + throw ex; + } } } /** - * This method enables parallel execution of queries from multiple threads on the same database. - * It does this by opening multiple handles to the database and using a different - * database handle for each query. + * Sets whether foreign key constraints are enabled for the database. * <p> - * If a transaction is in progress on one connection handle and say, a table is updated in the - * transaction, then query on the same table on another connection handle will block for the - * transaction to complete. But this method enables such queries to execute by having them - * return old version of the data from the table. Most often it is the data that existed in the - * table prior to the above transaction updates on that table. + * By default, foreign key constraints are not enforced by the database. + * This method allows an application to enable foreign key constraints. + * It must be called each time the database is opened to ensure that foreign + * key constraints are enabled for the session. + * </p><p> + * A good time to call this method is right after calling {@link #openOrCreateDatabase} + * or in the {@link SQLiteOpenHelper#onConfigure} callback. + * </p><p> + * When foreign key constraints are disabled, the database does not check whether + * changes to the database will violate foreign key constraints. Likewise, when + * foreign key constraints are disabled, the database will not execute cascade + * delete or update triggers. As a result, it is possible for the database + * state to become inconsistent. To perform a database integrity check, + * call {@link #isDatabaseIntegrityOk}. + * </p><p> + * This method must not be called while a transaction is in progress. + * </p><p> + * See also <a href="http://sqlite.org/foreignkeys.html">SQLite Foreign Key Constraints</a> + * for more details about foreign key constraint support. + * </p> + * + * @param enable True to enable foreign key constraints, false to disable them. + * + * @throws IllegalStateException if the are transactions is in progress + * when this method is called. + */ + public void setForeignKeyConstraintsEnabled(boolean enable) { + synchronized (mLock) { + throwIfNotOpenLocked(); + + if (mConfigurationLocked.foreignKeyConstraintsEnabled == enable) { + return; + } + + mConfigurationLocked.foreignKeyConstraintsEnabled = enable; + try { + mConnectionPoolLocked.reconfigure(mConfigurationLocked); + } catch (RuntimeException ex) { + mConfigurationLocked.foreignKeyConstraintsEnabled = !enable; + throw ex; + } + } + } + + /** + * This method enables parallel execution of queries from multiple threads on the + * same database. It does this by opening multiple connections to the database + * and using a different database connection for each query. The database + * journal mode is also changed to enable writes to proceed concurrently with reads. * <p> - * Maximum number of simultaneous handles used to execute queries in parallel is + * When write-ahead logging is not enabled (the default), it is not possible for + * reads and writes to occur on the database at the same time. Before modifying the + * database, the writer implicitly acquires an exclusive lock on the database which + * prevents readers from accessing the database until the write is completed. + * </p><p> + * In contrast, when write-ahead logging is enabled (by calling this method), write + * operations occur in a separate log file which allows reads to proceed concurrently. + * While a write is in progress, readers on other threads will perceive the state + * of the database as it was before the write began. When the write completes, readers + * on other threads will then perceive the new state of the database. + * </p><p> + * It is a good idea to enable write-ahead logging whenever a database will be + * concurrently accessed and modified by multiple threads at the same time. + * However, write-ahead logging uses significantly more memory than ordinary + * journaling because there are multiple connections to the same database. + * So if a database will only be used by a single thread, or if optimizing + * concurrency is not very important, then write-ahead logging should be disabled. + * </p><p> + * After calling this method, execution of queries in parallel is enabled as long as + * the database remains open. To disable execution of queries in parallel, either + * call {@link #disableWriteAheadLogging} or close the database and reopen it. + * </p><p> + * The maximum number of connections used to execute queries in parallel is * dependent upon the device memory and possibly other properties. - * <p> - * After calling this method, execution of queries in parallel is enabled as long as this - * database handle is open. To disable execution of queries in parallel, database should - * be closed and reopened. - * <p> + * </p><p> * If a query is part of a transaction, then it is executed on the same database handle the * transaction was begun. - * <p> - * If the database has any attached databases, then execution of queries in paralel is NOT - * possible. In such cases, a message is printed to logcat and false is returned. - * <p> - * This feature is not available for :memory: databases. In such cases, - * a message is printed to logcat and false is returned. - * <p> - * A typical way to use this method is the following: - * <pre> - * SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory, - * CREATE_IF_NECESSARY, myDatabaseErrorHandler); - * db.enableWriteAheadLogging(); - * </pre> - * <p> + * </p><p> * Writers should use {@link #beginTransactionNonExclusive()} or * {@link #beginTransactionWithListenerNonExclusive(SQLiteTransactionListener)} - * to start a trsnsaction. - * Non-exclusive mode allows database file to be in readable by threads executing queries. + * to start a transaction. Non-exclusive mode allows database file to be in readable + * by other threads executing queries. + * </p><p> + * If the database has any attached databases, then execution of queries in parallel is NOT + * possible. Likewise, write-ahead logging is not supported for read-only databases + * or memory databases. In such cases, {@link #enableWriteAheadLogging} returns false. + * </p><p> + * The best way to enable write-ahead logging is to pass the + * {@link #ENABLE_WRITE_AHEAD_LOGGING} flag to {@link #openDatabase}. This is + * more efficient than calling {@link #enableWriteAheadLogging}. + * <code><pre> + * SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory, + * SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING, + * myDatabaseErrorHandler); + * db.enableWriteAheadLogging(); + * </pre></code> + * </p><p> + * Another way to enable write-ahead logging is to call {@link #enableWriteAheadLogging} + * after opening the database. + * <code><pre> + * SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory, + * SQLiteDatabase.CREATE_IF_NECESSARY, myDatabaseErrorHandler); + * db.enableWriteAheadLogging(); + * </pre></code> + * </p><p> + * See also <a href="http://sqlite.org/wal.html">SQLite Write-Ahead Logging</a> for + * more details about how write-ahead logging works. * </p> * - * @return true if write-ahead-logging is set. false otherwise + * @return True if write-ahead logging is enabled. + * + * @throws IllegalStateException if there are transactions in progress at the + * time this method is called. WAL mode can only be changed when there are no + * transactions in progress. + * + * @see #ENABLE_WRITE_AHEAD_LOGGING + * @see #disableWriteAheadLogging */ public boolean enableWriteAheadLogging() { synchronized (mLock) { throwIfNotOpenLocked(); - if (mIsWALEnabledLocked) { + if ((mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0) { return true; } @@ -1835,32 +1942,57 @@ public final class SQLiteDatabase extends SQLiteClosable { return false; } - mIsWALEnabledLocked = true; - mConfigurationLocked.maxConnectionPoolSize = SQLiteGlobal.getWALConnectionPoolSize(); - mConfigurationLocked.syncMode = SQLiteGlobal.getWALSyncMode(); - mConfigurationLocked.journalMode = "WAL"; - mConnectionPoolLocked.reconfigure(mConfigurationLocked); + mConfigurationLocked.openFlags |= ENABLE_WRITE_AHEAD_LOGGING; + try { + mConnectionPoolLocked.reconfigure(mConfigurationLocked); + } catch (RuntimeException ex) { + mConfigurationLocked.openFlags &= ~ENABLE_WRITE_AHEAD_LOGGING; + throw ex; + } } return true; } /** * This method disables the features enabled by {@link #enableWriteAheadLogging()}. - * @hide + * + * @throws IllegalStateException if there are transactions in progress at the + * time this method is called. WAL mode can only be changed when there are no + * transactions in progress. + * + * @see #enableWriteAheadLogging */ public void disableWriteAheadLogging() { synchronized (mLock) { throwIfNotOpenLocked(); - if (!mIsWALEnabledLocked) { + if ((mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) == 0) { return; } - mIsWALEnabledLocked = false; - mConfigurationLocked.maxConnectionPoolSize = 1; - mConfigurationLocked.syncMode = SQLiteGlobal.getDefaultSyncMode(); - mConfigurationLocked.journalMode = SQLiteGlobal.getDefaultJournalMode(); - mConnectionPoolLocked.reconfigure(mConfigurationLocked); + mConfigurationLocked.openFlags &= ~ENABLE_WRITE_AHEAD_LOGGING; + try { + mConnectionPoolLocked.reconfigure(mConfigurationLocked); + } catch (RuntimeException ex) { + mConfigurationLocked.openFlags |= ENABLE_WRITE_AHEAD_LOGGING; + throw ex; + } + } + } + + /** + * Returns true if write-ahead logging has been enabled for this database. + * + * @return True if write-ahead logging has been enabled for this database. + * + * @see #enableWriteAheadLogging + * @see #ENABLE_WRITE_AHEAD_LOGGING + */ + public boolean isWriteAheadLoggingEnabled() { + synchronized (mLock) { + throwIfNotOpenLocked(); + + return (mConfigurationLocked.openFlags & ENABLE_WRITE_AHEAD_LOGGING) != 0; } } diff --git a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java index efbcaca..549ab90 100644 --- a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java +++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java @@ -62,14 +62,6 @@ public final class SQLiteDatabaseConfiguration { public int openFlags; /** - * The maximum number of connections to retain in the connection pool. - * Must be at least 1. - * - * Default is 1. - */ - public int maxConnectionPoolSize; - - /** * The maximum size of the prepared statement cache for each database connection. * Must be non-negative. * @@ -85,18 +77,11 @@ public final class SQLiteDatabaseConfiguration { public Locale locale; /** - * The database synchronization mode. - * - * Default is {@link SQLiteGlobal#getDefaultSyncMode()}. - */ - public String syncMode; - - /** - * The database journal mode. + * True if foreign key constraints are enabled. * - * Default is {@link SQLiteGlobal#getDefaultJournalMode()}. + * Default is false. */ - public String journalMode; + public boolean foreignKeyConstraintsEnabled; /** * The custom functions to register. @@ -121,11 +106,8 @@ public final class SQLiteDatabaseConfiguration { this.openFlags = openFlags; // Set default values for optional parameters. - maxConnectionPoolSize = 1; maxSqlCacheSize = 25; locale = Locale.getDefault(); - syncMode = SQLiteGlobal.getDefaultSyncMode(); - journalMode = SQLiteGlobal.getDefaultJournalMode(); } /** @@ -159,11 +141,9 @@ public final class SQLiteDatabaseConfiguration { } openFlags = other.openFlags; - maxConnectionPoolSize = other.maxConnectionPoolSize; maxSqlCacheSize = other.maxSqlCacheSize; locale = other.locale; - syncMode = other.syncMode; - journalMode = other.journalMode; + foreignKeyConstraintsEnabled = other.foreignKeyConstraintsEnabled; customFunctions.clear(); customFunctions.addAll(other.customFunctions); } diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java index ffa4663..431eca2 100644 --- a/core/java/android/database/sqlite/SQLiteOpenHelper.java +++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java @@ -58,6 +58,7 @@ public abstract class SQLiteOpenHelper { private SQLiteDatabase mDatabase; private boolean mIsInitializing; + private boolean mEnableWriteAheadLogging; private final DatabaseErrorHandler mErrorHandler; /** @@ -74,7 +75,7 @@ public abstract class SQLiteOpenHelper { * newer, {@link #onDowngrade} will be used to downgrade the database */ public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) { - this(context, name, factory, version, new DefaultDatabaseErrorHandler()); + this(context, name, factory, version, null); } /** @@ -92,14 +93,11 @@ public abstract class SQLiteOpenHelper { * {@link #onUpgrade} will be used to upgrade the database; if the database is * newer, {@link #onDowngrade} will be used to downgrade the database * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database - * corruption. + * corruption, or null to use the default error handler. */ public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version, DatabaseErrorHandler errorHandler) { if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version); - if (errorHandler == null) { - throw new IllegalArgumentException("DatabaseErrorHandler param value can't be null."); - } mContext = context; mName = name; @@ -117,6 +115,32 @@ public abstract class SQLiteOpenHelper { } /** + * Enables or disables the use of write-ahead logging for the database. + * + * Write-ahead logging cannot be used with read-only databases so the value of + * this flag is ignored if the database is opened read-only. + * + * @param enabled True if write-ahead logging should be enabled, false if it + * should be disabled. + * + * @see SQLiteDatabase#enableWriteAheadLogging() + */ + public void setWriteAheadLoggingEnabled(boolean enabled) { + synchronized (this) { + if (mEnableWriteAheadLogging != enabled) { + if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) { + if (enabled) { + mDatabase.enableWriteAheadLogging(); + } else { + mDatabase.disableWriteAheadLogging(); + } + } + mEnableWriteAheadLogging = enabled; + } + } + } + + /** * Create and/or open a database that will be used for reading and writing. * The first time this is called, the database will be opened and * {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be @@ -197,7 +221,9 @@ public abstract class SQLiteOpenHelper { db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY, mErrorHandler); } else { - db = mContext.openOrCreateDatabase(mName, 0, mFactory, mErrorHandler); + db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ? + Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0, + mFactory, mErrorHandler); } } catch (SQLiteException ex) { if (writable) { @@ -211,6 +237,8 @@ public abstract class SQLiteOpenHelper { } } + onConfigure(db); + final int version = db.getVersion(); if (version != mNewVersion) { if (db.isReadOnly()) { @@ -235,6 +263,7 @@ public abstract class SQLiteOpenHelper { db.endTransaction(); } } + onOpen(db); if (db.isReadOnly()) { @@ -264,6 +293,25 @@ public abstract class SQLiteOpenHelper { } /** + * Called when the database connection is being configured, to enable features + * such as write-ahead logging or foreign key support. + * <p> + * This method is called before {@link #onCreate}, {@link #onUpgrade}, + * {@link #onDowngrade}, or {@link #onOpen} are called. It should not modify + * the database except to configure the database connection as required. + * </p><p> + * This method should only call methods that configure the parameters of the + * database connection, such as {@link SQLiteDatabase#enableWriteAheadLogging} + * {@link SQLiteDatabase#setForeignKeyConstraintsEnabled}, + * {@link SQLiteDatabase#setLocale}, {@link SQLiteDatabase#setMaximumSize}, + * or executing PRAGMA statements. + * </p> + * + * @param db The database. + */ + public void onConfigure(SQLiteDatabase db) {} + + /** * Called when the database is created for the first time. This is where the * creation of tables and the initial population of the tables should happen. * @@ -276,11 +324,16 @@ public abstract class SQLiteOpenHelper { * should use this method to drop tables, add tables, or do anything else it * needs to upgrade to the new schema version. * - * <p>The SQLite ALTER TABLE documentation can be found + * <p> + * The SQLite ALTER TABLE documentation can be found * <a href="http://sqlite.org/lang_altertable.html">here</a>. If you add new columns * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns * you can use ALTER TABLE to rename the old table, then create the new table and then * populate the new table with the contents of the old table. + * </p><p> + * This method executes within a transaction. If an exception is thrown, all changes + * will automatically be rolled back. + * </p> * * @param db The database. * @param oldVersion The old database version. @@ -290,11 +343,16 @@ public abstract class SQLiteOpenHelper { /** * Called when the database needs to be downgraded. This is strictly similar to - * onUpgrade() method, but is called whenever current version is newer than requested one. + * {@link #onUpgrade} method, but is called whenever current version is newer than requested one. * However, this method is not abstract, so it is not mandatory for a customer to * implement it. If not overridden, default implementation will reject downgrade and * throws SQLiteException * + * <p> + * This method executes within a transaction. If an exception is thrown, all changes + * will automatically be rolled back. + * </p> + * * @param db The database. * @param oldVersion The old database version. * @param newVersion The new database version. @@ -308,6 +366,12 @@ public abstract class SQLiteOpenHelper { * Called when the database has been opened. The implementation * should check {@link SQLiteDatabase#isReadOnly} before updating the * database. + * <p> + * This method is called after the database connection has been configured + * and after the database schema has been created, upgraded or downgraded as necessary. + * If the database connection must be configured in some way before the schema + * is created, upgraded, or downgraded, do it in {@link #onConfigure} instead. + * </p> * * @param db The database. */ diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java index 43efb03..9410243 100644 --- a/core/java/android/database/sqlite/SQLiteSession.java +++ b/core/java/android/database/sqlite/SQLiteSession.java @@ -398,16 +398,16 @@ public final class SQLiteSession { throwIfNoTransaction(); assert mConnection != null; - endTransactionUnchecked(cancellationSignal); + endTransactionUnchecked(cancellationSignal, false); } - private void endTransactionUnchecked(CancellationSignal cancellationSignal) { + private void endTransactionUnchecked(CancellationSignal cancellationSignal, boolean yielding) { if (cancellationSignal != null) { cancellationSignal.throwIfCanceled(); } final Transaction top = mTransactionStack; - boolean successful = top.mMarkedSuccessful && !top.mChildFailed; + boolean successful = (top.mMarkedSuccessful || yielding) && !top.mChildFailed; RuntimeException listenerException = null; final SQLiteTransactionListener listener = top.mListener; @@ -534,7 +534,7 @@ public final class SQLiteSession { final int transactionMode = mTransactionStack.mMode; final SQLiteTransactionListener listener = mTransactionStack.mListener; final int connectionFlags = mConnectionFlags; - endTransactionUnchecked(cancellationSignal); // might throw + endTransactionUnchecked(cancellationSignal, true); // might throw if (sleepAfterYieldDelayMillis > 0) { try { diff --git a/core/java/android/net/Downloads.java b/core/java/android/net/Downloads.java deleted file mode 100644 index ed6d103..0000000 --- a/core/java/android/net/Downloads.java +++ /dev/null @@ -1,644 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.net; - -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.os.SystemClock; -import android.provider.BaseColumns; -import android.util.Log; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.File; -import java.io.InputStream; - -/** - * The Download Manager - * - * @hide - */ -public final class Downloads { - - - /** - * Download status codes - */ - - /** - * This download hasn't started yet - */ - public static final int STATUS_PENDING = 190; - - /** - * This download has started - */ - public static final int STATUS_RUNNING = 192; - - /** - * This download has successfully completed. - * Warning: there might be other status values that indicate success - * in the future. - * Use isSucccess() to capture the entire category. - */ - public static final int STATUS_SUCCESS = 200; - - /** - * This download can't be performed because the content type cannot be - * handled. - */ - public static final int STATUS_NOT_ACCEPTABLE = 406; - - /** - * This download has completed with an error. - * Warning: there will be other status values that indicate errors in - * the future. Use isStatusError() to capture the entire category. - */ - public static final int STATUS_UNKNOWN_ERROR = 491; - - /** - * This download couldn't be completed because of an HTTP - * redirect response that the download manager couldn't - * handle. - */ - public static final int STATUS_UNHANDLED_REDIRECT = 493; - - /** - * This download couldn't be completed due to insufficient storage - * space. Typically, this is because the SD card is full. - */ - public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498; - - /** - * This download couldn't be completed because no external storage - * device was found. Typically, this is because the SD card is not - * mounted. - */ - public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499; - - /** - * Returns whether the status is a success (i.e. 2xx). - */ - public static boolean isStatusSuccess(int status) { - return (status >= 200 && status < 300); - } - - /** - * Returns whether the status is an error (i.e. 4xx or 5xx). - */ - public static boolean isStatusError(int status) { - return (status >= 400 && status < 600); - } - - /** - * Download destinations - */ - - /** - * This download will be saved to the external storage. This is the - * default behavior, and should be used for any file that the user - * can freely access, copy, delete. Even with that destination, - * unencrypted DRM files are saved in secure internal storage. - * Downloads to the external destination only write files for which - * there is a registered handler. The resulting files are accessible - * by filename to all applications. - */ - public static final int DOWNLOAD_DESTINATION_EXTERNAL = 1; - - /** - * This download will be saved to the download manager's private - * partition. This is the behavior used by applications that want to - * download private files that are used and deleted soon after they - * get downloaded. All file types are allowed, and only the initiating - * application can access the file (indirectly through a content - * provider). This requires the - * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED permission. - */ - public static final int DOWNLOAD_DESTINATION_CACHE = 2; - - /** - * This download will be saved to the download manager's private - * partition and will be purged as necessary to make space. This is - * for private files (similar to CACHE_PARTITION) that aren't deleted - * immediately after they are used, and are kept around by the download - * manager as long as space is available. - */ - public static final int DOWNLOAD_DESTINATION_CACHE_PURGEABLE = 3; - - - /** - * An invalid download id - */ - public static final long DOWNLOAD_ID_INVALID = -1; - - - /** - * Broadcast Action: this is sent by the download manager to the app - * that had initiated a download when that download completes. The - * download's content: uri is specified in the intent's data. - */ - public static final String ACTION_DOWNLOAD_COMPLETED = - "android.intent.action.DOWNLOAD_COMPLETED"; - - /** - * If extras are specified when requesting a download they will be provided in the intent that - * is sent to the specified class and package when a download has finished. - * <P>Type: TEXT</P> - * <P>Owner can Init</P> - */ - public static final String COLUMN_NOTIFICATION_EXTRAS = "notificationextras"; - - - /** - * Status class for a download - */ - public static final class StatusInfo { - public boolean completed = false; - /** The filename of the active download. */ - public String filename = null; - /** An opaque id for the download */ - public long id = DOWNLOAD_ID_INVALID; - /** An opaque status code for the download */ - public int statusCode = -1; - /** Approximate number of bytes downloaded so far, for debugging purposes. */ - public long bytesSoFar = -1; - - /** - * Returns whether the download is completed - * @return a boolean whether the download is complete. - */ - public boolean isComplete() { - return android.provider.Downloads.Impl.isStatusCompleted(statusCode); - } - - /** - * Returns whether the download is successful - * @return a boolean whether the download is successful. - */ - public boolean isSuccessful() { - return android.provider.Downloads.Impl.isStatusSuccess(statusCode); - } - } - - /** - * Class to access initiate and query download by server uri - */ - public static final class ByUri extends DownloadBase { - /** @hide */ - private ByUri() {} - - /** - * Query where clause by app data. - * @hide - */ - private static final String QUERY_WHERE_APP_DATA_CLAUSE = - android.provider.Downloads.Impl.COLUMN_APP_DATA + "=?"; - - /** - * Gets a Cursor pointing to the download(s) of the current system update. - * @hide - */ - private static final Cursor getCurrentOtaDownloads(Context context, String url) { - return context.getContentResolver().query( - android.provider.Downloads.Impl.CONTENT_URI, - DOWNLOADS_PROJECTION, - QUERY_WHERE_APP_DATA_CLAUSE, - new String[] {url}, - null); - } - - /** - * Returns a StatusInfo with the result of trying to download the - * given URL. Returns null if no attempts have been made. - */ - public static final StatusInfo getStatus( - Context context, - String url, - long redownload_threshold) { - StatusInfo result = null; - boolean hasFailedDownload = false; - long failedDownloadModificationTime = 0; - Cursor c = getCurrentOtaDownloads(context, url); - try { - while (c != null && c.moveToNext()) { - if (result == null) { - result = new StatusInfo(); - } - int status = getStatusOfDownload(c, redownload_threshold); - if (status == STATUS_DOWNLOADING_UPDATE || - status == STATUS_DOWNLOADED_UPDATE) { - result.completed = (status == STATUS_DOWNLOADED_UPDATE); - result.filename = c.getString(DOWNLOADS_COLUMN_FILENAME); - result.id = c.getLong(DOWNLOADS_COLUMN_ID); - result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS); - result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES); - return result; - } - - long modTime = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION); - if (hasFailedDownload && - modTime < failedDownloadModificationTime) { - // older than the one already in result; skip it. - continue; - } - - hasFailedDownload = true; - failedDownloadModificationTime = modTime; - result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS); - result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES); - } - } finally { - if (c != null) { - c.close(); - } - } - return result; - } - - /** - * Query where clause for general querying. - */ - private static final String QUERY_WHERE_CLAUSE = - android.provider.Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE + "=? AND " + - android.provider.Downloads.Impl.COLUMN_NOTIFICATION_CLASS + "=?"; - - /** - * Delete all the downloads for a package/class pair. - */ - public static final void removeAllDownloadsByPackage( - Context context, - String notification_package, - String notification_class) { - context.getContentResolver().delete( - android.provider.Downloads.Impl.CONTENT_URI, - QUERY_WHERE_CLAUSE, - new String[] { notification_package, notification_class }); - } - - /** - * The column for the id in the Cursor returned by - * getProgressCursor() - */ - public static final int getProgressColumnId() { - return 0; - } - - /** - * The column for the current byte count in the Cursor returned by - * getProgressCursor() - */ - public static final int getProgressColumnCurrentBytes() { - return 1; - } - - /** - * The column for the total byte count in the Cursor returned by - * getProgressCursor() - */ - public static final int getProgressColumnTotalBytes() { - return 2; - } - - /** @hide */ - private static final String[] PROJECTION = { - BaseColumns._ID, - android.provider.Downloads.Impl.COLUMN_CURRENT_BYTES, - android.provider.Downloads.Impl.COLUMN_TOTAL_BYTES - }; - - /** - * Returns a Cursor representing the progress of the download identified by the ID. - */ - public static final Cursor getProgressCursor(Context context, long id) { - Uri downloadUri = Uri.withAppendedPath(android.provider.Downloads.Impl.CONTENT_URI, - String.valueOf(id)); - return context.getContentResolver().query(downloadUri, PROJECTION, null, null, null); - } - } - - /** - * Class to access downloads by opaque download id - */ - public static final class ById extends DownloadBase { - /** @hide */ - private ById() {} - - /** - * Get the mime tupe of the download specified by the download id - */ - public static String getMimeTypeForId(Context context, long downloadId) { - ContentResolver cr = context.getContentResolver(); - - String mimeType = null; - Cursor downloadCursor = null; - - try { - Uri downloadUri = getDownloadUri(downloadId); - - downloadCursor = cr.query( - downloadUri, new String[]{android.provider.Downloads.Impl.COLUMN_MIME_TYPE}, - null, null, null); - if (downloadCursor.moveToNext()) { - mimeType = downloadCursor.getString(0); - } - } finally { - if (downloadCursor != null) downloadCursor.close(); - } - return mimeType; - } - - /** - * Delete a download by Id - */ - public static void deleteDownload(Context context, long downloadId) { - ContentResolver cr = context.getContentResolver(); - - String mimeType = null; - - Uri downloadUri = getDownloadUri(downloadId); - - cr.delete(downloadUri, null, null); - } - - /** - * Open a filedescriptor to a particular download - */ - public static ParcelFileDescriptor openDownload( - Context context, long downloadId, String mode) - throws FileNotFoundException - { - ContentResolver cr = context.getContentResolver(); - - String mimeType = null; - - Uri downloadUri = getDownloadUri(downloadId); - - return cr.openFileDescriptor(downloadUri, mode); - } - - /** - * Open a stream to a particular download - */ - public static InputStream openDownloadStream(Context context, long downloadId) - throws FileNotFoundException, IOException - { - ContentResolver cr = context.getContentResolver(); - - String mimeType = null; - - Uri downloadUri = getDownloadUri(downloadId); - - return cr.openInputStream(downloadUri); - } - - private static Uri getDownloadUri(long downloadId) { - return Uri.parse(android.provider.Downloads.Impl.CONTENT_URI + "/" + downloadId); - } - - /** - * Returns a StatusInfo with the result of trying to download the - * given URL. Returns null if no attempts have been made. - */ - public static final StatusInfo getStatus( - Context context, - long downloadId) { - StatusInfo result = null; - boolean hasFailedDownload = false; - long failedDownloadModificationTime = 0; - - Uri downloadUri = getDownloadUri(downloadId); - - ContentResolver cr = context.getContentResolver(); - - Cursor c = cr.query(downloadUri, DOWNLOADS_PROJECTION, null /* selection */, - null /* selection args */, null /* sort order */); - try { - if (c == null || !c.moveToNext()) { - return result; - } - - if (result == null) { - result = new StatusInfo(); - } - int status = getStatusOfDownload(c,0); - if (status == STATUS_DOWNLOADING_UPDATE || - status == STATUS_DOWNLOADED_UPDATE) { - result.completed = (status == STATUS_DOWNLOADED_UPDATE); - result.filename = c.getString(DOWNLOADS_COLUMN_FILENAME); - result.id = c.getLong(DOWNLOADS_COLUMN_ID); - result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS); - result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES); - return result; - } - - long modTime = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION); - - result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS); - result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES); - } finally { - if (c != null) { - c.close(); - } - } - return result; - } - } - - - /** - * Base class with common functionality for the various download classes - */ - public static class DownloadBase { - /** @hide */ - DownloadBase() {} - - /** - * Initiate a download where the download will be tracked by its URI. - */ - public static long startDownloadByUri( - Context context, - String url, - String cookieData, - boolean showDownload, - int downloadDestination, - boolean allowRoaming, - boolean skipIntegrityCheck, - String title, - String notification_package, - String notification_class, - String notification_extras) { - ContentResolver cr = context.getContentResolver(); - - // Tell download manager to start downloading update. - ContentValues values = new ContentValues(); - values.put(android.provider.Downloads.Impl.COLUMN_URI, url); - values.put(android.provider.Downloads.Impl.COLUMN_COOKIE_DATA, cookieData); - values.put(android.provider.Downloads.Impl.COLUMN_VISIBILITY, - showDownload ? android.provider.Downloads.Impl.VISIBILITY_VISIBLE - : android.provider.Downloads.Impl.VISIBILITY_HIDDEN); - if (title != null) { - values.put(android.provider.Downloads.Impl.COLUMN_TITLE, title); - } - values.put(android.provider.Downloads.Impl.COLUMN_APP_DATA, url); - - - // NOTE: destination should be seperated from whether the download - // can happen when roaming - int destination = android.provider.Downloads.Impl.DESTINATION_EXTERNAL; - switch (downloadDestination) { - case DOWNLOAD_DESTINATION_EXTERNAL: - destination = android.provider.Downloads.Impl.DESTINATION_EXTERNAL; - break; - case DOWNLOAD_DESTINATION_CACHE: - if (allowRoaming) { - destination = android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION; - } else { - destination = - android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING; - } - break; - case DOWNLOAD_DESTINATION_CACHE_PURGEABLE: - destination = - android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE; - break; - } - values.put(android.provider.Downloads.Impl.COLUMN_DESTINATION, destination); - values.put(android.provider.Downloads.Impl.COLUMN_NO_INTEGRITY, - skipIntegrityCheck); // Don't check ETag - if (notification_package != null && notification_class != null) { - values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, - notification_package); - values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_CLASS, - notification_class); - - if (notification_extras != null) { - values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS, - notification_extras); - } - } - - Uri downloadUri = cr.insert(android.provider.Downloads.Impl.CONTENT_URI, values); - - long downloadId = DOWNLOAD_ID_INVALID; - if (downloadUri != null) { - downloadId = Long.parseLong(downloadUri.getLastPathSegment()); - } - return downloadId; - } - } - - /** @hide */ - private static final int STATUS_INVALID = 0; - /** @hide */ - private static final int STATUS_DOWNLOADING_UPDATE = 3; - /** @hide */ - private static final int STATUS_DOWNLOADED_UPDATE = 4; - - /** - * Column projection for the query to the download manager. This must match - * with the constants DOWNLOADS_COLUMN_*. - * @hide - */ - private static final String[] DOWNLOADS_PROJECTION = { - BaseColumns._ID, - android.provider.Downloads.Impl.COLUMN_APP_DATA, - android.provider.Downloads.Impl.COLUMN_STATUS, - android.provider.Downloads.Impl._DATA, - android.provider.Downloads.Impl.COLUMN_LAST_MODIFICATION, - android.provider.Downloads.Impl.COLUMN_CURRENT_BYTES, - }; - - /** - * The column index for the ID. - * @hide - */ - private static final int DOWNLOADS_COLUMN_ID = 0; - /** - * The column index for the URI. - * @hide - */ - private static final int DOWNLOADS_COLUMN_URI = 1; - /** - * The column index for the status code. - * @hide - */ - private static final int DOWNLOADS_COLUMN_STATUS = 2; - /** - * The column index for the filename. - * @hide - */ - private static final int DOWNLOADS_COLUMN_FILENAME = 3; - /** - * The column index for the last modification time. - * @hide - */ - private static final int DOWNLOADS_COLUMN_LAST_MODIFICATION = 4; - /** - * The column index for the number of bytes downloaded so far. - * @hide - */ - private static final int DOWNLOADS_COLUMN_CURRENT_BYTES = 5; - - /** - * Gets the status of a download. - * - * @param c A Cursor pointing to a download. The URL column is assumed to be valid. - * @return The status of the download. - * @hide - */ - private static final int getStatusOfDownload( Cursor c, long redownload_threshold) { - int status = c.getInt(DOWNLOADS_COLUMN_STATUS); - long realtime = SystemClock.elapsedRealtime(); - - // TODO(dougz): special handling of 503, 404? (eg, special - // explanatory messages to user) - - if (!android.provider.Downloads.Impl.isStatusCompleted(status)) { - // Check if it's stuck - long modified = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION); - long now = System.currentTimeMillis(); - if (now < modified || now - modified > redownload_threshold) { - return STATUS_INVALID; - } - - return STATUS_DOWNLOADING_UPDATE; - } - - if (android.provider.Downloads.Impl.isStatusError(status)) { - return STATUS_INVALID; - } - - String filename = c.getString(DOWNLOADS_COLUMN_FILENAME); - if (filename == null) { - return STATUS_INVALID; - } - - return STATUS_DOWNLOADED_UPDATE; - } - - - /** - * @hide - */ - private Downloads() {} -} diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl index 442535a..89c9c36 100644 --- a/core/java/android/net/INetworkPolicyManager.aidl +++ b/core/java/android/net/INetworkPolicyManager.aidl @@ -30,8 +30,8 @@ import android.net.NetworkTemplate; interface INetworkPolicyManager { /** Control UID policies. */ - void setUidPolicy(int uid, int policy); - int getUidPolicy(int uid); + void setAppPolicy(int appId, int policy); + int getAppPolicy(int appId); boolean isUidForeground(int uid); diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java index 5b94784..c1f58a3 100644 --- a/core/java/android/net/NetworkPolicy.java +++ b/core/java/android/net/NetworkPolicy.java @@ -42,18 +42,20 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { public long lastWarningSnooze; public long lastLimitSnooze; public boolean metered; + public boolean inferred; private static final long DEFAULT_MTU = 1500; + @Deprecated public NetworkPolicy(NetworkTemplate template, int cycleDay, String cycleTimezone, long warningBytes, long limitBytes, boolean metered) { this(template, cycleDay, cycleTimezone, warningBytes, limitBytes, SNOOZE_NEVER, - SNOOZE_NEVER, metered); + SNOOZE_NEVER, metered, false); } public NetworkPolicy(NetworkTemplate template, int cycleDay, String cycleTimezone, long warningBytes, long limitBytes, long lastWarningSnooze, long lastLimitSnooze, - boolean metered) { + boolean metered, boolean inferred) { this.template = checkNotNull(template, "missing NetworkTemplate"); this.cycleDay = cycleDay; this.cycleTimezone = checkNotNull(cycleTimezone, "missing cycleTimezone"); @@ -62,6 +64,7 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { this.lastWarningSnooze = lastWarningSnooze; this.lastLimitSnooze = lastLimitSnooze; this.metered = metered; + this.inferred = inferred; } public NetworkPolicy(Parcel in) { @@ -73,6 +76,7 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { lastWarningSnooze = in.readLong(); lastLimitSnooze = in.readLong(); metered = in.readInt() != 0; + inferred = in.readInt() != 0; } @Override @@ -85,6 +89,7 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { dest.writeLong(lastWarningSnooze); dest.writeLong(lastLimitSnooze); dest.writeInt(metered ? 1 : 0); + dest.writeInt(inferred ? 1 : 0); } @Override @@ -134,7 +139,7 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { @Override public int hashCode() { return Objects.hashCode(template, cycleDay, cycleTimezone, warningBytes, limitBytes, - lastWarningSnooze, lastLimitSnooze, metered); + lastWarningSnooze, lastLimitSnooze, metered, inferred); } @Override @@ -145,6 +150,7 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { && limitBytes == other.limitBytes && lastWarningSnooze == other.lastWarningSnooze && lastLimitSnooze == other.lastLimitSnooze && metered == other.metered + && inferred == other.inferred && Objects.equal(cycleTimezone, other.cycleTimezone) && Objects.equal(template, other.template); } @@ -156,7 +162,7 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { return "NetworkPolicy[" + template + "]: cycleDay=" + cycleDay + ", cycleTimezone=" + cycleTimezone + ", warningBytes=" + warningBytes + ", limitBytes=" + limitBytes + ", lastWarningSnooze=" + lastWarningSnooze + ", lastLimitSnooze=" - + lastLimitSnooze + ", metered=" + metered; + + lastLimitSnooze + ", metered=" + metered + ", inferred=" + inferred; } public static final Creator<NetworkPolicy> CREATOR = new Creator<NetworkPolicy>() { diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 7173751..c09c676 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -88,21 +88,21 @@ public class NetworkPolicyManager { } /** - * Set policy flags for specific UID. + * Set policy flags for specific application. * * @param policy {@link #POLICY_NONE} or combination of flags like * {@link #POLICY_REJECT_METERED_BACKGROUND}. */ - public void setUidPolicy(int uid, int policy) { + public void setAppPolicy(int appId, int policy) { try { - mService.setUidPolicy(uid, policy); + mService.setAppPolicy(appId, policy); } catch (RemoteException e) { } } - public int getUidPolicy(int uid) { + public int getAppPolicy(int appId) { try { - return mService.getUidPolicy(uid); + return mService.getAppPolicy(appId); } catch (RemoteException e) { return POLICY_NONE; } @@ -203,6 +203,7 @@ public class NetworkPolicyManager { * Check if given UID can have a {@link #setUidPolicy(int, int)} defined, * usually to protect critical system services. */ + @Deprecated public static boolean isUidValidForPolicy(Context context, int uid) { // first, quick-reject non-applications if (uid < android.os.Process.FIRST_APPLICATION_UID diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java index 5c4b258..6a4f1f2 100644 --- a/core/java/android/net/SSLCertificateSocketFactory.java +++ b/core/java/android/net/SSLCertificateSocketFactory.java @@ -18,13 +18,11 @@ package android.net; import android.os.SystemProperties; import android.util.Log; - import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.security.KeyManagementException; import java.security.cert.X509Certificate; - import javax.net.SocketFactory; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; @@ -36,7 +34,6 @@ import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; - import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl; import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl; import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache; @@ -89,6 +86,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { private SSLSocketFactory mSecureFactory = null; private TrustManager[] mTrustManagers = null; private KeyManager[] mKeyManagers = null; + private byte[] mNpnProtocols = null; private final int mHandshakeTimeoutMillis; private final SSLClientSessionCache mSessionCache; @@ -251,6 +249,60 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { } /** + * Sets the <a href="http://technotes.googlecode.com/git/nextprotoneg.html">Next + * Protocol Negotiation (NPN)</a> protocols that this peer is interested in. + * + * <p>For servers this is the sequence of protocols to advertise as + * supported, in order of preference. This list is sent unencrypted to + * all clients that support NPN. + * + * <p>For clients this is a list of supported protocols to match against the + * server's list. If there is no protocol supported by both client and + * server then the first protocol in the client's list will be selected. + * The order of the client's protocols is otherwise insignificant. + * + * @param npnProtocols a possibly-empty list of protocol byte arrays. All + * arrays must be non-empty and of length less than 256. + */ + public void setNpnProtocols(byte[][] npnProtocols) { + this.mNpnProtocols = toNpnProtocolsList(npnProtocols); + } + + /** + * Returns an array containing the concatenation of length-prefixed byte + * strings. + */ + static byte[] toNpnProtocolsList(byte[]... npnProtocols) { + int totalLength = 0; + for (byte[] s : npnProtocols) { + if (s.length == 0 || s.length > 255) { + throw new IllegalArgumentException("s.length == 0 || s.length > 255: " + s.length); + } + totalLength += 1 + s.length; + } + byte[] result = new byte[totalLength]; + int pos = 0; + for (byte[] s : npnProtocols) { + result[pos++] = (byte) s.length; + for (byte b : s) { + result[pos++] = b; + } + } + return result; + } + + /** + * Returns the <a href="http://technotes.googlecode.com/git/nextprotoneg.html">Next + * Protocol Negotiation (NPN)</a> protocol selected by client and server, or + * null if no protocol was negotiated. + * + * @param socket a socket created by this factory. + */ + public byte[] getNpnSelectedProtocol(Socket socket) { + return ((OpenSSLSocketImpl) socket).getNpnSelectedProtocol(); + } + + /** * Sets the {@link KeyManager}s to be used for connections made by this factory. */ public void setKeyManagers(KeyManager[] keyManagers) { @@ -271,6 +323,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { @Override public Socket createSocket(Socket k, String host, int port, boolean close) throws IOException { OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(k, host, port, close); + s.setNpnProtocols(mNpnProtocols); s.setHandshakeTimeout(mHandshakeTimeoutMillis); if (mSecure) { verifyHostname(s, host); @@ -289,6 +342,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { @Override public Socket createSocket() throws IOException { OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(); + s.setNpnProtocols(mNpnProtocols); s.setHandshakeTimeout(mHandshakeTimeoutMillis); return s; } @@ -305,6 +359,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { throws IOException { OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket( addr, port, localAddr, localPort); + s.setNpnProtocols(mNpnProtocols); s.setHandshakeTimeout(mHandshakeTimeoutMillis); return s; } @@ -319,6 +374,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { @Override public Socket createSocket(InetAddress addr, int port) throws IOException { OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(addr, port); + s.setNpnProtocols(mNpnProtocols); s.setHandshakeTimeout(mHandshakeTimeoutMillis); return s; } @@ -334,6 +390,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { throws IOException { OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket( host, port, localAddr, localPort); + s.setNpnProtocols(mNpnProtocols); s.setHandshakeTimeout(mHandshakeTimeoutMillis); if (mSecure) { verifyHostname(s, host); @@ -350,6 +407,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { @Override public Socket createSocket(String host, int port) throws IOException { OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(host, port); + s.setNpnProtocols(mNpnProtocols); s.setHandshakeTimeout(mHandshakeTimeoutMillis); if (mSecure) { verifyHostname(s, host); diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index 28206b7..51cb91c 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -226,6 +226,13 @@ public final class Bundle implements Parcelable, Cloneable { } /** + * @hide + */ + public boolean isParcelled() { + return mParcelledData != null; + } + + /** * Returns the number of mappings contained in this Bundle. * * @return the number of mappings as an int. diff --git a/core/java/android/os/UserId.java b/core/java/android/os/UserId.java index 0da67d6..8bf6c6e 100644 --- a/core/java/android/os/UserId.java +++ b/core/java/android/os/UserId.java @@ -61,6 +61,15 @@ public final class UserId { return uid >= Process.FIRST_ISOLATED_UID && uid <= Process.LAST_ISOLATED_UID; } + public static boolean isApp(int uid) { + if (uid > 0) { + uid = UserId.getAppId(uid); + return uid >= Process.FIRST_APPLICATION_UID && uid <= Process.LAST_APPLICATION_UID; + } else { + return false; + } + } + /** * Returns the user id for a given uid. * @hide @@ -96,4 +105,12 @@ public final class UserId { public static final int getAppId(int uid) { return uid % PER_USER_RANGE; } + + /** + * Returns the user id of the current process + * @return user id of the current process + */ + public static final int myUserId() { + return getUserId(Process.myUid()); + } } diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java index ba4804d..bd6170b 100644 --- a/core/java/android/provider/Downloads.java +++ b/core/java/android/provider/Downloads.java @@ -17,6 +17,7 @@ package android.provider; import android.app.DownloadManager; +import android.content.Context; import android.net.NetworkPolicyManager; import android.net.Uri; @@ -742,4 +743,19 @@ public final class Downloads { public static final String INSERT_KEY_PREFIX = "http_header_"; } } + + /** + * Query where clause for general querying. + */ + private static final String QUERY_WHERE_CLAUSE = Impl.COLUMN_NOTIFICATION_PACKAGE + "=? AND " + + Impl.COLUMN_NOTIFICATION_CLASS + "=?"; + + /** + * Delete all the downloads for a package/class pair. + */ + public static final void removeAllDownloadsByPackage( + Context context, String notification_package, String notification_class) { + context.getContentResolver().delete(Impl.CONTENT_URI, QUERY_WHERE_CLAUSE, + new String[] { notification_package, notification_class }); + } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index fbb3273..d74ccb8 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -793,6 +793,7 @@ public final class Settings { MOVED_TO_SECURE.add(Secure.HTTP_PROXY); MOVED_TO_SECURE.add(Secure.INSTALL_NON_MARKET_APPS); MOVED_TO_SECURE.add(Secure.LOCATION_PROVIDERS_ALLOWED); + MOVED_TO_SECURE.add(Secure.LOCK_BIOMETRIC_WEAK_FLAGS); MOVED_TO_SECURE.add(Secure.LOCK_PATTERN_ENABLED); MOVED_TO_SECURE.add(Secure.LOCK_PATTERN_VISIBLE); MOVED_TO_SECURE.add(Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED); @@ -2657,6 +2658,13 @@ public final class Settings { public static final String LOCATION_PROVIDERS_ALLOWED = "location_providers_allowed"; /** + * A flag containing settings used for biometric weak + * @hide + */ + public static final String LOCK_BIOMETRIC_WEAK_FLAGS = + "lock_biometric_weak_flags"; + + /** * Whether autolock is enabled (0 = false, 1 = true) */ public static final String LOCK_PATTERN_ENABLED = "lock_pattern_autolock"; diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index fecc8f9..850349b 100755 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -46,6 +46,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.res.Resources; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -554,12 +555,15 @@ public class BluetoothService extends IBluetooth.Stub { private synchronized void updateSdpRecords() { ArrayList<ParcelUuid> uuids = new ArrayList<ParcelUuid>(); + Resources R = mContext.getResources(); + // Add the default records - uuids.add(BluetoothUuid.HSP_AG); - uuids.add(BluetoothUuid.ObexObjectPush); + if (R.getBoolean(com.android.internal.R.bool.config_bluetooth_default_profiles)) { + uuids.add(BluetoothUuid.HSP_AG); + uuids.add(BluetoothUuid.ObexObjectPush); + } - if (mContext.getResources(). - getBoolean(com.android.internal.R.bool.config_voice_capable)) { + if (R.getBoolean(com.android.internal.R.bool.config_voice_capable)) { uuids.add(BluetoothUuid.Handsfree_AG); uuids.add(BluetoothUuid.PBAP_PSE); } @@ -567,14 +571,16 @@ public class BluetoothService extends IBluetooth.Stub { // Add SDP records for profiles maintained by Android userspace addReservedSdpRecords(uuids); - // Enable profiles maintained by Bluez userspace. - setBluetoothTetheringNative(true, BluetoothPanProfileHandler.NAP_ROLE, - BluetoothPanProfileHandler.NAP_BRIDGE); + if (R.getBoolean(com.android.internal.R.bool.config_bluetooth_default_profiles)) { + // Enable profiles maintained by Bluez userspace. + setBluetoothTetheringNative(true, BluetoothPanProfileHandler.NAP_ROLE, + BluetoothPanProfileHandler.NAP_BRIDGE); - // Add SDP records for profiles maintained by Bluez userspace - uuids.add(BluetoothUuid.AudioSource); - uuids.add(BluetoothUuid.AvrcpTarget); - uuids.add(BluetoothUuid.NAP); + // Add SDP records for profiles maintained by Bluez userspace + uuids.add(BluetoothUuid.AudioSource); + uuids.add(BluetoothUuid.AvrcpTarget); + uuids.add(BluetoothUuid.NAP); + } // Cannot cast uuids.toArray directly since ParcelUuid is parcelable mAdapterUuids = new ParcelUuid[uuids.size()]; diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java index 1dabad2..e2aafa9 100644 --- a/core/java/android/view/DisplayList.java +++ b/core/java/android/view/DisplayList.java @@ -16,8 +16,6 @@ package android.view; -import android.os.Handler; - /** * A display lists records a series of graphics related operation and can replay * them later. Display lists are usually built by recording operations on a @@ -37,6 +35,30 @@ public abstract class DisplayList { */ public static final int FLAG_CLIP_CHILDREN = 0x1; + // NOTE: The STATUS_* values *must* match the enum in DrawGlInfo.h + + /** + * Indicates that the display list is done drawing. + * + * @see HardwareCanvas#drawDisplayList(DisplayList, int, int, android.graphics.Rect, int) + */ + public static final int STATUS_DONE = 0x0; + + /** + * Indicates that the display list needs another drawing pass. + * + * @see HardwareCanvas#drawDisplayList(DisplayList, int, int, android.graphics.Rect, int) + */ + public static final int STATUS_DRAW = 0x1; + + /** + * Indicates that the display list needs to re-execute its GL functors. + * + * @see HardwareCanvas#drawDisplayList(DisplayList, int, int, android.graphics.Rect, int) + * @see HardwareCanvas#callDrawGLFunction(int) + */ + public static final int STATUS_INVOKE = 0x2; + /** * Starts recording the display list. All operations performed on the * returned canvas are recorded and stored in this display list. diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java index d9bf918..9639faf 100644 --- a/core/java/android/view/FocusFinder.java +++ b/core/java/android/view/FocusFinder.java @@ -79,25 +79,45 @@ public class FocusFinder { switch (direction) { case View.FOCUS_RIGHT: case View.FOCUS_DOWN: + setFocusBottomRight(root); + break; case View.FOCUS_FORWARD: - final int rootTop = root.getScrollY(); - final int rootLeft = root.getScrollX(); - mFocusedRect.set(rootLeft, rootTop, rootLeft, rootTop); + if (focused != null && focused.isLayoutRtl()) { + setFocusTopLeft(root); + } else { + setFocusBottomRight(root); + } break; case View.FOCUS_LEFT: case View.FOCUS_UP: + setFocusTopLeft(root); + break; case View.FOCUS_BACKWARD: - final int rootBottom = root.getScrollY() + root.getHeight(); - final int rootRight = root.getScrollX() + root.getWidth(); - mFocusedRect.set(rootRight, rootBottom, - rootRight, rootBottom); + if (focused != null && focused.isLayoutRtl()) { + setFocusBottomRight(root); + } else { + setFocusTopLeft(root); break; + } } } return findNextFocus(root, focused, mFocusedRect, direction); } + private void setFocusTopLeft(ViewGroup root) { + final int rootBottom = root.getScrollY() + root.getHeight(); + final int rootRight = root.getScrollX() + root.getWidth(); + mFocusedRect.set(rootRight, rootBottom, + rootRight, rootBottom); + } + + private void setFocusBottomRight(ViewGroup root) { + final int rootTop = root.getScrollY(); + final int rootLeft = root.getScrollX(); + mFocusedRect.set(rootLeft, rootTop, rootLeft, rootTop); + } + /** * Find the next view to take focus in root's descendants, searching from * a particular rectangle in root's coordinates. @@ -135,22 +155,10 @@ public class FocusFinder { final int count = focusables.size(); switch (direction) { case View.FOCUS_FORWARD: - if (focused != null) { - int position = focusables.lastIndexOf(focused); - if (position >= 0 && position + 1 < count) { - return focusables.get(position + 1); - } - } - return focusables.get(0); + return getForwardFocusable(focused, focusables, count); case View.FOCUS_BACKWARD: - if (focused != null) { - int position = focusables.indexOf(focused); - if (position > 0) { - return focusables.get(position - 1); - } - } - return focusables.get(count - 1); + return getBackwardFocusable(focused, focusables, count); } return null; } @@ -193,6 +201,38 @@ public class FocusFinder { return closest; } + private View getForwardFocusable(View focused, ArrayList<View> focusables, int count) { + return (focused != null && focused.isLayoutRtl()) ? + getPreviousFocusable(focused, focusables, count) : + getNextFocusable(focused, focusables, count); + } + + private View getNextFocusable(View focused, ArrayList<View> focusables, int count) { + if (focused != null) { + int position = focusables.lastIndexOf(focused); + if (position >= 0 && position + 1 < count) { + return focusables.get(position + 1); + } + } + return focusables.get(0); + } + + private View getBackwardFocusable(View focused, ArrayList<View> focusables, int count) { + return (focused != null && focused.isLayoutRtl()) ? + getNextFocusable(focused, focusables, count) : + getPreviousFocusable(focused, focusables, count); + } + + private View getPreviousFocusable(View focused, ArrayList<View> focusables, int count) { + if (focused != null) { + int position = focusables.indexOf(focused); + if (position > 0) { + return focusables.get(position - 1); + } + } + return focusables.get(count - 1); + } + /** * Is rect1 a better candidate than rect2 for a focus search in a particular * direction from a source rect? This is the core routine that determines diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 1f75e70..0e96742 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -296,11 +296,11 @@ class GLES20Canvas extends HardwareCanvas { /////////////////////////////////////////////////////////////////////////// @Override - public boolean callDrawGLFunction(int drawGLFunction) { + public int callDrawGLFunction(int drawGLFunction) { return nCallDrawGLFunction(mRenderer, drawGLFunction); } - private static native boolean nCallDrawGLFunction(int renderer, int drawGLFunction); + private static native int nCallDrawGLFunction(int renderer, int drawGLFunction); /////////////////////////////////////////////////////////////////////////// // Memory @@ -394,13 +394,13 @@ class GLES20Canvas extends HardwareCanvas { private static native void nSetDisplayListName(int displayList, String name); @Override - public boolean drawDisplayList(DisplayList displayList, int width, int height, + public int drawDisplayList(DisplayList displayList, int width, int height, Rect dirty, int flags) { return nDrawDisplayList(mRenderer, ((GLES20DisplayList) displayList).getNativeDisplayList(), width, height, dirty, flags); } - private static native boolean nDrawDisplayList(int renderer, int displayList, + private static native int nDrawDisplayList(int renderer, int displayList, int width, int height, Rect dirty, int flags); @Override diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java index 838c03c..2636ea2 100644 --- a/core/java/android/view/HardwareCanvas.java +++ b/core/java/android/view/HardwareCanvas.java @@ -59,11 +59,11 @@ public abstract class HardwareCanvas extends Canvas { * if this method returns true, can be null. * @param flags Optional flags about drawing, see {@link DisplayList} for * the possible flags. - * - * @return True if the content of the display list requires another - * drawing pass (invalidate()), false otherwise + * + * @return One of {@link DisplayList#STATUS_DONE}, {@link DisplayList#STATUS_DRAW} or + * {@link DisplayList#STATUS_INVOKE} */ - public abstract boolean drawDisplayList(DisplayList displayList, int width, int height, + public abstract int drawDisplayList(DisplayList displayList, int width, int height, Rect dirty, int flags); /** @@ -90,10 +90,12 @@ public abstract class HardwareCanvas extends Canvas { * This function may return true if an invalidation is needed after the call. * * @param drawGLFunction A native function pointer - * @return true if an invalidate is needed after the call, false otherwise + * + * @return One of {@link DisplayList#STATUS_DONE}, {@link DisplayList#STATUS_DRAW} or + * {@link DisplayList#STATUS_INVOKE} */ - public boolean callDrawGLFunction(int drawGLFunction) { + public int callDrawGLFunction(int drawGLFunction) { // Noop - this is done in the display list recorder subclass - return false; + return DisplayList.STATUS_DONE; } } diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index d08a61f..d40043f 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -464,8 +464,8 @@ public abstract class HardwareRenderer { static final Object[] sEglLock = new Object[0]; int mWidth = -1, mHeight = -1; - static final ThreadLocal<Gl20Renderer.Gl20RendererEglContext> sEglContextStorage - = new ThreadLocal<Gl20Renderer.Gl20RendererEglContext>(); + static final ThreadLocal<ManagedEGLContext> sEglContextStorage + = new ThreadLocal<ManagedEGLContext>(); EGLContext mEglContext; Thread mEglThread; @@ -622,7 +622,7 @@ public abstract class HardwareRenderer { } } - abstract GLES20Canvas createCanvas(); + abstract HardwareCanvas createCanvas(); abstract int[] getConfig(boolean dirtyRegions); @@ -662,16 +662,18 @@ public abstract class HardwareRenderer { } } - Gl20Renderer.Gl20RendererEglContext managedContext = sEglContextStorage.get(); + ManagedEGLContext managedContext = sEglContextStorage.get(); mEglContext = managedContext != null ? managedContext.getContext() : null; mEglThread = Thread.currentThread(); if (mEglContext == null) { mEglContext = createContext(sEgl, sEglDisplay, sEglConfig); - sEglContextStorage.set(new Gl20Renderer.Gl20RendererEglContext(mEglContext)); + sEglContextStorage.set(createManagedContext(mEglContext)); } } + abstract ManagedEGLContext createManagedContext(EGLContext eglContext); + private EGLConfig chooseEglConfig() { EGLConfig[] configs = new EGLConfig[1]; int[] configsCount = new int[1]; @@ -704,7 +706,7 @@ public abstract class HardwareRenderer { return null; } - private void printConfig(EGLConfig config) { + private static void printConfig(EGLConfig config) { int[] value = new int[1]; Log.d(LOG_TAG, "EGL configuration " + config + ":"); @@ -964,7 +966,6 @@ public abstract class HardwareRenderer { Log.d("DLProperties", "getDisplayList():\t" + mProfileData[mProfileCurrentFrame]); } - } if (displayList != null) { @@ -973,7 +974,7 @@ public abstract class HardwareRenderer { drawDisplayListStartTime = System.nanoTime(); } - boolean invalidateNeeded = canvas.drawDisplayList(displayList, + int status = canvas.drawDisplayList(displayList, view.getWidth(), view.getHeight(), mRedrawClip, DisplayList.FLAG_CLIP_CHILDREN); @@ -984,18 +985,18 @@ public abstract class HardwareRenderer { if (ViewDebug.DEBUG_LATENCY) { Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- drawDisplayList() took " + - total + "ms, invalidateNeeded=" + - invalidateNeeded + "."); + total + "ms, status=" + status); } } - if (invalidateNeeded) { - if (mRedrawClip.isEmpty() || view.getParent() == null) { - view.invalidate(); + if (status != DisplayList.STATUS_DONE) { + if (mRedrawClip.isEmpty()) { + attachInfo.mViewRootImpl.invalidate(); } else { - view.getParent().invalidateChild(view, mRedrawClip); + attachInfo.mViewRootImpl.invalidateChildInParent( + null, mRedrawClip); + mRedrawClip.setEmpty(); } - mRedrawClip.setEmpty(); } } else { // Shouldn't reach here @@ -1102,7 +1103,8 @@ public abstract class HardwareRenderer { // Make sure we do this on the correct thread. if (mHandler.getLooper() != Looper.myLooper()) { mHandler.post(new Runnable() { - @Override public void run() { + @Override + public void run() { onTerminate(eglContext); } }); @@ -1117,6 +1119,7 @@ public abstract class HardwareRenderer { GLES20Canvas.terminateCaches(); sEgl.eglDestroyContext(sEglDisplay, eglContext); + sEglContextStorage.set(null); sEglContextStorage.remove(); sEgl.eglDestroySurface(sEglDisplay, sPbuffer); @@ -1130,7 +1133,6 @@ public abstract class HardwareRenderer { sEglDisplay = null; sEglConfig = null; sPbuffer = null; - sEglContextStorage.set(null); } } } @@ -1141,11 +1143,16 @@ public abstract class HardwareRenderer { } @Override - GLES20Canvas createCanvas() { + HardwareCanvas createCanvas() { return mGlCanvas = new GLES20Canvas(mTranslucent); } @Override + ManagedEGLContext createManagedContext(EGLContext eglContext) { + return new Gl20Renderer.Gl20RendererEglContext(mEglContext); + } + + @Override int[] getConfig(boolean dirtyRegions) { return new int[] { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, @@ -1229,7 +1236,7 @@ public abstract class HardwareRenderer { } private static void destroyHardwareLayer(View view) { - view.destroyLayer(); + view.destroyLayer(true); if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup) view; @@ -1248,7 +1255,8 @@ public abstract class HardwareRenderer { if (isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) needsContext = false; if (needsContext) { - Gl20RendererEglContext managedContext = sEglContextStorage.get(); + Gl20RendererEglContext managedContext = + (Gl20RendererEglContext) sEglContextStorage.get(); if (managedContext == null) return; usePbufferSurface(managedContext.getContext()); } @@ -1281,7 +1289,8 @@ public abstract class HardwareRenderer { static void trimMemory(int level) { if (sEgl == null || sEglConfig == null) return; - Gl20RendererEglContext managedContext = sEglContextStorage.get(); + Gl20RendererEglContext managedContext = + (Gl20RendererEglContext) sEglContextStorage.get(); // We do not have OpenGL objects if (managedContext == null) { return; diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index c54d09e..14cd48f 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -89,6 +89,8 @@ interface IWindowManager void prepareAppTransition(int transit, boolean alwaysKeepCurrent); int getPendingAppTransition(); void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim); + void overridePendingAppTransitionThumb(in Bitmap srcThumb, int startX, int startY, + IRemoteCallback startedCallback); void executeAppTransition(); void setAppStartingWindow(IBinder token, String pkg, int theme, in CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 92e8f4e..77fd8d2 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -1654,14 +1654,22 @@ public final class MotionEvent extends InputEvent implements Parcelable { } } } - + /** - * Scales down the coordination of this event by the given scale. + * Applies a scale factor to all points within this event. + * + * This method is used to adjust touch events to simulate different density + * displays for compatibility mode. The values returned by {@link #getRawX()}, + * {@link #getRawY()}, {@link #getXPrecision()} and {@link #getYPrecision()} + * are also affected by the scale factor. * + * @param scale The scale factor to apply. * @hide */ public final void scale(float scale) { - nativeScale(mNativePtr, scale); + if (scale != 1.0f) { + nativeScale(mNativePtr, scale); + } } /** {@inheritDoc} */ @@ -2631,7 +2639,9 @@ public final class MotionEvent extends InputEvent implements Parcelable { * @param deltaY Amount to add to the current Y coordinate of the event. */ public final void offsetLocation(float deltaX, float deltaY) { - nativeOffsetLocation(mNativePtr, deltaX, deltaY); + if (deltaX != 0.0f || deltaY != 0.0f) { + nativeOffsetLocation(mNativePtr, deltaX, deltaY); + } } /** @@ -2644,7 +2654,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { public final void setLocation(float x, float y) { float oldX = getX(); float oldY = getY(); - nativeOffsetLocation(mNativePtr, x - oldX, y - oldY); + offsetLocation(x - oldX, y - oldY); } /** diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index edaa262..eb80290d 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -20,6 +20,7 @@ import android.content.res.CompatibilityInfo.Translator; import android.graphics.*; import android.os.Parcelable; import android.os.Parcel; +import android.os.SystemProperties; import android.util.Log; /** @@ -35,6 +36,15 @@ public class Surface implements Parcelable { public static final int ROTATION_180 = 2; public static final int ROTATION_270 = 3; + private static final boolean headless = "1".equals( + SystemProperties.get("ro.config.headless", "0")); + + private static void checkHeadless() { + if(headless) { + throw new UnsupportedOperationException("Device is headless"); + } + } + /** * Create Surface from a {@link SurfaceTexture}. * @@ -46,6 +56,8 @@ public class Surface implements Parcelable { * Surface. */ public Surface(SurfaceTexture surfaceTexture) { + checkHeadless(); + if (DEBUG_RELEASE) { mCreationStack = new Exception(); } @@ -244,6 +256,8 @@ public class Surface implements Parcelable { public Surface(SurfaceSession s, int pid, int display, int w, int h, int format, int flags) throws OutOfResourcesException { + checkHeadless(); + if (DEBUG_RELEASE) { mCreationStack = new Exception(); } @@ -255,6 +269,8 @@ public class Surface implements Parcelable { public Surface(SurfaceSession s, int pid, String name, int display, int w, int h, int format, int flags) throws OutOfResourcesException { + checkHeadless(); + if (DEBUG_RELEASE) { mCreationStack = new Exception(); } @@ -269,6 +285,8 @@ public class Surface implements Parcelable { * @hide */ public Surface() { + checkHeadless(); + if (DEBUG_RELEASE) { mCreationStack = new Exception(); } diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index fc02cc1..83999a1 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -299,7 +299,7 @@ public class TextureView extends View { } @Override - boolean destroyLayer() { + boolean destroyLayer(boolean valid) { return false; } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index fdf3a814..2deeba6 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -698,14 +698,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal private static final int[] VISIBILITY_FLAGS = {VISIBLE, INVISIBLE, GONE}; /** - * This view is enabled. Intrepretation varies by subclass. + * This view is enabled. Interpretation varies by subclass. * Use with ENABLED_MASK when calling setFlags. * {@hide} */ static final int ENABLED = 0x00000000; /** - * This view is disabled. Intrepretation varies by subclass. + * This view is disabled. Interpretation varies by subclass. * Use with ENABLED_MASK when calling setFlags. * {@hide} */ @@ -955,50 +955,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal static final int PARENT_SAVE_DISABLED_MASK = 0x20000000; /** - * Horizontal direction of this view is from Left to Right. - * Use with {@link #setLayoutDirection}. - */ - public static final int LAYOUT_DIRECTION_LTR = 0x00000000; - - /** - * Horizontal direction of this view is from Right to Left. - * Use with {@link #setLayoutDirection}. - */ - public static final int LAYOUT_DIRECTION_RTL = 0x40000000; - - /** - * Horizontal direction of this view is inherited from its parent. - * Use with {@link #setLayoutDirection}. - */ - public static final int LAYOUT_DIRECTION_INHERIT = 0x80000000; - - /** - * Horizontal direction of this view is from deduced from the default language - * script for the locale. Use with {@link #setLayoutDirection}. - */ - public static final int LAYOUT_DIRECTION_LOCALE = 0xC0000000; - - /** - * Mask for use with setFlags indicating bits used for horizontalDirection. - * {@hide} - */ - static final int LAYOUT_DIRECTION_MASK = 0xC0000000; - - /* - * Array of horizontal direction flags for mapping attribute "horizontalDirection" to correct - * flag value. - * {@hide} - */ - private static final int[] LAYOUT_DIRECTION_FLAGS = {LAYOUT_DIRECTION_LTR, - LAYOUT_DIRECTION_RTL, LAYOUT_DIRECTION_INHERIT, LAYOUT_DIRECTION_LOCALE}; - - /** - * Default horizontalDirection. - * {@hide} - */ - private static final int LAYOUT_DIRECTION_DEFAULT = LAYOUT_DIRECTION_INHERIT; - - /** * View flag indicating whether {@link #addFocusables(ArrayList, int, int)} * should add all focusable Views regardless if they are focusable in touch mode. */ @@ -1748,19 +1704,77 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal static final int DRAG_HOVERED = 0x00000002; /** - * Indicates whether the view layout direction has been resolved and drawn to the + * Horizontal layout direction of this view is from Left to Right. + * Use with {@link #setLayoutDirection}. + */ + public static final int LAYOUT_DIRECTION_LTR = 0; + + /** + * Horizontal layout direction of this view is from Right to Left. + * Use with {@link #setLayoutDirection}. + */ + public static final int LAYOUT_DIRECTION_RTL = 1; + + /** + * Horizontal layout direction of this view is inherited from its parent. + * Use with {@link #setLayoutDirection}. + */ + public static final int LAYOUT_DIRECTION_INHERIT = 2; + + /** + * Horizontal layout direction of this view is from deduced from the default language + * script for the locale. Use with {@link #setLayoutDirection}. + */ + public static final int LAYOUT_DIRECTION_LOCALE = 3; + + /** + * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED) + * @hide + */ + static final int LAYOUT_DIRECTION_MASK_SHIFT = 2; + + /** + * Mask for use with private flags indicating bits used for horizontal layout direction. + * @hide + */ + static final int LAYOUT_DIRECTION_MASK = 0x00000003 << LAYOUT_DIRECTION_MASK_SHIFT; + + /** + * Indicates whether the view horizontal layout direction has been resolved and drawn to the * right-to-left direction. - * * @hide */ - static final int LAYOUT_DIRECTION_RESOLVED_RTL = 0x00000004; + static final int LAYOUT_DIRECTION_RESOLVED_RTL = 4 << LAYOUT_DIRECTION_MASK_SHIFT; /** - * Indicates whether the view layout direction has been resolved. - * + * Indicates whether the view horizontal layout direction has been resolved. + * @hide + */ + static final int LAYOUT_DIRECTION_RESOLVED = 8 << LAYOUT_DIRECTION_MASK_SHIFT; + + /** + * Mask for use with private flags indicating bits used for resolved horizontal layout direction. * @hide */ - static final int LAYOUT_DIRECTION_RESOLVED = 0x00000008; + static final int LAYOUT_DIRECTION_RESOLVED_MASK = 0x0000000C << LAYOUT_DIRECTION_MASK_SHIFT; + + /* + * Array of horizontal layout direction flags for mapping attribute "layoutDirection" to correct + * flag value. + * @hide + */ + private static final int[] LAYOUT_DIRECTION_FLAGS = { + LAYOUT_DIRECTION_LTR, + LAYOUT_DIRECTION_RTL, + LAYOUT_DIRECTION_INHERIT, + LAYOUT_DIRECTION_LOCALE + }; + + /** + * Default horizontal layout direction. + * @hide + */ + private static final int LAYOUT_DIRECTION_DEFAULT = LAYOUT_DIRECTION_INHERIT; /** @@ -1770,7 +1784,98 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @hide */ - static final int HAS_TRANSIENT_STATE = 0x00000010; + static final int HAS_TRANSIENT_STATE = 0x00000100; + + + /** + * Text direction is inherited thru {@link ViewGroup} + */ + public static final int TEXT_DIRECTION_INHERIT = 0; + + /** + * Text direction is using "first strong algorithm". The first strong directional character + * determines the paragraph direction. If there is no strong directional character, the + * paragraph direction is the view's resolved layout direction. + */ + public static final int TEXT_DIRECTION_FIRST_STRONG = 1; + + /** + * Text direction is using "any-RTL" algorithm. The paragraph direction is RTL if it contains + * any strong RTL character, otherwise it is LTR if it contains any strong LTR characters. + * If there are neither, the paragraph direction is the view's resolved layout direction. + */ + public static final int TEXT_DIRECTION_ANY_RTL = 2; + + /** + * Text direction is forced to LTR. + */ + public static final int TEXT_DIRECTION_LTR = 3; + + /** + * Text direction is forced to RTL. + */ + public static final int TEXT_DIRECTION_RTL = 4; + + /** + * Text direction is coming from the system Locale. + */ + public static final int TEXT_DIRECTION_LOCALE = 5; + + /** + * Bit shift to get the horizontal layout direction. (bits after LAYOUT_DIRECTION_RESOLVED) + * @hide + */ + static final int TEXT_DIRECTION_MASK_SHIFT = 6; + + /** + * Default text direction is inherited + */ + protected static int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT; + + /** + * Mask for use with private flags indicating bits used for text direction. + * @hide + */ + static final int TEXT_DIRECTION_MASK = 0x00000007 << TEXT_DIRECTION_MASK_SHIFT; + + /** + * Array of text direction flags for mapping attribute "textDirection" to correct + * flag value. + * @hide + */ + private static final int[] TEXT_DIRECTION_FLAGS = { + TEXT_DIRECTION_INHERIT << TEXT_DIRECTION_MASK_SHIFT, + TEXT_DIRECTION_FIRST_STRONG << TEXT_DIRECTION_MASK_SHIFT, + TEXT_DIRECTION_ANY_RTL << TEXT_DIRECTION_MASK_SHIFT, + TEXT_DIRECTION_LTR << TEXT_DIRECTION_MASK_SHIFT, + TEXT_DIRECTION_RTL << TEXT_DIRECTION_MASK_SHIFT, + TEXT_DIRECTION_LOCALE << TEXT_DIRECTION_MASK_SHIFT + }; + + /** + * Indicates whether the view text direction has been resolved. + * @hide + */ + static final int TEXT_DIRECTION_RESOLVED = 0x00000008 << TEXT_DIRECTION_MASK_SHIFT; + + /** + * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED) + * @hide + */ + static final int TEXT_DIRECTION_RESOLVED_MASK_SHIFT = 10; + + /** + * Mask for use with private flags indicating bits used for resolved text direction. + * @hide + */ + static final int TEXT_DIRECTION_RESOLVED_MASK = 0x00000007 << TEXT_DIRECTION_RESOLVED_MASK_SHIFT; + + /** + * Indicates whether the view text direction has been resolved to the "first strong" heuristic. + * @hide + */ + static final int TEXT_DIRECTION_RESOLVED_DEFAULT = + TEXT_DIRECTION_FIRST_STRONG << TEXT_DIRECTION_RESOLVED_MASK_SHIFT; /* End of masks for mPrivateFlags2 */ @@ -2647,82 +2752,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal AccessibilityDelegate mAccessibilityDelegate; /** - * Text direction is inherited thru {@link ViewGroup} - */ - public static final int TEXT_DIRECTION_INHERIT = 0; - - /** - * Text direction is using "first strong algorithm". The first strong directional character - * determines the paragraph direction. If there is no strong directional character, the - * paragraph direction is the view's resolved layout direction. - * - */ - public static final int TEXT_DIRECTION_FIRST_STRONG = 1; - - /** - * Text direction is using "any-RTL" algorithm. The paragraph direction is RTL if it contains - * any strong RTL character, otherwise it is LTR if it contains any strong LTR characters. - * If there are neither, the paragraph direction is the view's resolved layout direction. - * - */ - public static final int TEXT_DIRECTION_ANY_RTL = 2; - - /** - * Text direction is forced to LTR. - * - */ - public static final int TEXT_DIRECTION_LTR = 3; - - /** - * Text direction is forced to RTL. - * - */ - public static final int TEXT_DIRECTION_RTL = 4; - - /** - * Text direction is coming from the system Locale. - * - */ - public static final int TEXT_DIRECTION_LOCALE = 5; - - /** - * Default text direction is inherited - * - */ - protected static int DEFAULT_TEXT_DIRECTION = TEXT_DIRECTION_INHERIT; - - /** - * The text direction that has been defined by {@link #setTextDirection(int)}. - * - */ - @ViewDebug.ExportedProperty(category = "text", mapping = { - @ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"), - @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG, to = "FIRST_STRONG"), - @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"), - @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"), - @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL"), - @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE") - }) - private int mTextDirection = DEFAULT_TEXT_DIRECTION; - - /** - * The resolved text direction. This needs resolution if the value is - * TEXT_DIRECTION_INHERIT. The resolution matches mTextDirection if it is - * not TEXT_DIRECTION_INHERIT, otherwise resolution proceeds up the parent - * chain of the view. - * - */ - @ViewDebug.ExportedProperty(category = "text", mapping = { - @ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"), - @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG, to = "FIRST_STRONG"), - @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"), - @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"), - @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL"), - @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE") - }) - private int mResolvedTextDirection = TEXT_DIRECTION_INHERIT; - - /** * Consistency verifier for debugging purposes. * @hide */ @@ -2739,7 +2768,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public View(Context context) { mContext = context; mResources = context != null ? context.getResources() : null; - mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | LAYOUT_DIRECTION_INHERIT; + mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED; + // Set layout and text direction defaults + mPrivateFlags2 = (LAYOUT_DIRECTION_DEFAULT << LAYOUT_DIRECTION_MASK_SHIFT) | + (TEXT_DIRECTION_DEFAULT << TEXT_DIRECTION_MASK_SHIFT); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS); mUserPaddingStart = -1; @@ -2949,17 +2981,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } break; case com.android.internal.R.styleable.View_layoutDirection: - // Clear any HORIZONTAL_DIRECTION flag already set - viewFlagValues &= ~LAYOUT_DIRECTION_MASK; - // Set the HORIZONTAL_DIRECTION flags depending on the value of the attribute + // Clear any layout direction flags (included resolved bits) already set + mPrivateFlags2 &= ~(LAYOUT_DIRECTION_MASK | LAYOUT_DIRECTION_RESOLVED_MASK); + // Set the layout direction flags depending on the value of the attribute final int layoutDirection = a.getInt(attr, -1); - if (layoutDirection != -1) { - viewFlagValues |= LAYOUT_DIRECTION_FLAGS[layoutDirection]; - } else { - // Set to default (LAYOUT_DIRECTION_INHERIT) - viewFlagValues |= LAYOUT_DIRECTION_DEFAULT; - } - viewFlagMasks |= LAYOUT_DIRECTION_MASK; + final int value = (layoutDirection != -1) ? + LAYOUT_DIRECTION_FLAGS[layoutDirection] : LAYOUT_DIRECTION_DEFAULT; + mPrivateFlags2 |= (value << LAYOUT_DIRECTION_MASK_SHIFT); break; case com.android.internal.R.styleable.View_drawingCacheQuality: final int cacheQuality = a.getInt(attr, 0); @@ -3103,7 +3131,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal setLayerType(a.getInt(attr, LAYER_TYPE_NONE), null); break; case R.styleable.View_textDirection: - mTextDirection = a.getInt(attr, DEFAULT_TEXT_DIRECTION); + // Clear any text direction flag already set + mPrivateFlags2 &= ~TEXT_DIRECTION_MASK; + // Set the text direction flags depending on the value of the attribute + final int textDirection = a.getInt(attr, -1); + if (textDirection != -1) { + mPrivateFlags2 |= TEXT_DIRECTION_FLAGS[textDirection]; + } break; } } @@ -4882,7 +4916,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LOCALE, to = "LOCALE") }) public int getLayoutDirection() { - return mViewFlags & LAYOUT_DIRECTION_MASK; + return (mPrivateFlags2 & LAYOUT_DIRECTION_MASK) >> LAYOUT_DIRECTION_MASK_SHIFT; } /** @@ -4899,9 +4933,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal @RemotableViewMethod public void setLayoutDirection(int layoutDirection) { if (getLayoutDirection() != layoutDirection) { + // Reset the current layout direction and the resolved one + mPrivateFlags2 &= ~LAYOUT_DIRECTION_MASK; resetResolvedLayoutDirection(); - // Setting the flag will also request a layout. - setFlags(layoutDirection, LAYOUT_DIRECTION_MASK); + // Set the new layout direction (filtered) and ask for a layout pass + mPrivateFlags2 |= + ((layoutDirection << LAYOUT_DIRECTION_MASK_SHIFT) & LAYOUT_DIRECTION_MASK); + requestLayout(); } } @@ -4909,21 +4947,24 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Returns the resolved layout direction for this view. * * @return {@link #LAYOUT_DIRECTION_RTL} if the layout direction is RTL or returns - * {@link #LAYOUT_DIRECTION_LTR} id the layout direction is not RTL. + * {@link #LAYOUT_DIRECTION_LTR} if the layout direction is not RTL. */ @ViewDebug.ExportedProperty(category = "layout", mapping = { - @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LTR, to = "RESOLVED_DIRECTION_LTR"), - @ViewDebug.IntToString(from = LAYOUT_DIRECTION_RTL, to = "RESOLVED_DIRECTION_RTL") + @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LTR, to = "RESOLVED_DIRECTION_LTR"), + @ViewDebug.IntToString(from = LAYOUT_DIRECTION_RTL, to = "RESOLVED_DIRECTION_RTL") }) public int getResolvedLayoutDirection() { - resolveLayoutDirectionIfNeeded(); + // The layout diretion will be resolved only if needed + if ((mPrivateFlags2 & LAYOUT_DIRECTION_RESOLVED) != LAYOUT_DIRECTION_RESOLVED) { + resolveLayoutDirection(); + } return ((mPrivateFlags2 & LAYOUT_DIRECTION_RESOLVED_RTL) == LAYOUT_DIRECTION_RESOLVED_RTL) ? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR; } /** - * <p>Indicates whether or not this view's layout is right-to-left. This is resolved from - * layout attribute and/or the inherited value from the parent.</p> + * Indicates whether or not this view's layout is right-to-left. This is resolved from + * layout attribute and/or the inherited value from the parent * * @return true if the layout is right-to-left. */ @@ -6940,10 +6981,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mParent.recomputeViewAttributes(this); } } - - if ((changed & LAYOUT_DIRECTION_MASK) != 0) { - requestLayout(); - } } /** @@ -7364,7 +7401,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal invalidateViewProperty(false, false); if (USE_DISPLAY_LIST_PROPERTIES && mDisplayList != null) { - mDisplayList.setCameraDistance(distance); + mDisplayList.setCameraDistance(-Math.abs(distance) / dpi); } } @@ -9797,7 +9834,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal jumpDrawablesToCurrentState(); // Order is important here: LayoutDirection MUST be resolved before Padding // and TextDirection - resolveLayoutDirectionIfNeeded(); + resolveLayoutDirection(); resolvePadding(); resolveTextDirection(); if (isFocused()) { @@ -9828,31 +9865,24 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Resolve and cache the layout direction. LTR is set initially. This is implicitly supposing * that the parent directionality can and will be resolved before its children. + * Will call {@link View#onResolvedLayoutDirectionChanged} when resolution is done. */ - private void resolveLayoutDirectionIfNeeded() { - // Do not resolve if it is not needed - if ((mPrivateFlags2 & LAYOUT_DIRECTION_RESOLVED) == LAYOUT_DIRECTION_RESOLVED) return; - + public void resolveLayoutDirection() { // Clear any previous layout direction resolution - mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED_RTL; + mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED_MASK; // Set resolved depending on layout direction switch (getLayoutDirection()) { case LAYOUT_DIRECTION_INHERIT: - // We cannot do the resolution if there is no parent - if (mParent == null) return; - // If this is root view, no need to look at parent's layout dir. - if (mParent instanceof ViewGroup) { + if (canResolveLayoutDirection()) { ViewGroup viewGroup = ((ViewGroup) mParent); - // Check if the parent view group can resolve - if (! viewGroup.canResolveLayoutDirection()) { - return; - } if (viewGroup.getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) { mPrivateFlags2 |= LAYOUT_DIRECTION_RESOLVED_RTL; } + } else { + // Nothing to do, LTR by default } break; case LAYOUT_DIRECTION_RTL: @@ -9955,7 +9985,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public boolean canResolveLayoutDirection() { switch (getLayoutDirection()) { case LAYOUT_DIRECTION_INHERIT: - return (mParent != null); + return (mParent != null) && (mParent instanceof ViewGroup); default: return true; } @@ -9966,8 +9996,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * when reset is done. */ public void resetResolvedLayoutDirection() { - // Reset the current View resolution - mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED; + // Reset the current resolved bits + mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED_MASK; onResolvedLayoutDirectionReset(); // Reset also the text direction resetResolvedTextDirection(); @@ -10010,7 +10040,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal destroyDrawingCache(); - destroyLayer(); + destroyLayer(false); if (mAttachInfo != null) { if (mDisplayList != null) { @@ -10386,7 +10416,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // Destroy any previous software drawing cache if needed switch (mLayerType) { case LAYER_TYPE_HARDWARE: - destroyLayer(); + destroyLayer(false); // fall through - non-accelerated views may use software layer mechanism instead case LAYER_TYPE_SOFTWARE: destroyDrawingCache(); @@ -10524,11 +10554,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #setLayerType(int, android.graphics.Paint) * @see #LAYER_TYPE_HARDWARE */ - boolean destroyLayer() { + boolean destroyLayer(boolean valid) { if (mHardwareLayer != null) { AttachInfo info = mAttachInfo; if (info != null && info.mHardwareRenderer != null && - info.mHardwareRenderer.isEnabled() && info.mHardwareRenderer.validate()) { + info.mHardwareRenderer.isEnabled() && + (valid || info.mHardwareRenderer.validate())) { mHardwareLayer.destroy(); mHardwareLayer = null; @@ -10552,7 +10583,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @hide */ protected void destroyHardwareResources() { - destroyLayer(); + destroyLayer(true); } /** @@ -10716,16 +10747,25 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal int layerType = ( !(mParent instanceof ViewGroup) || ((ViewGroup)mParent).mDrawLayers) ? getLayerType() : LAYER_TYPE_NONE; - if (!isLayer && layerType == LAYER_TYPE_HARDWARE && USE_DISPLAY_LIST_PROPERTIES) { - final HardwareLayer layer = getHardwareLayer(); - if (layer != null && layer.isValid()) { - canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint); + if (!isLayer && layerType != LAYER_TYPE_NONE && USE_DISPLAY_LIST_PROPERTIES) { + if (layerType == LAYER_TYPE_HARDWARE) { + final HardwareLayer layer = getHardwareLayer(); + if (layer != null && layer.isValid()) { + canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint); + } else { + canvas.saveLayer(0, 0, mRight - mLeft, mBottom - mTop, mLayerPaint, + Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | + Canvas.CLIP_TO_LAYER_SAVE_FLAG); + } + caching = true; } else { - canvas.saveLayer(0, 0, - mRight - mLeft, mBottom - mTop, mLayerPaint, - Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG); + buildDrawingCache(true); + Bitmap cache = getDrawingCache(true); + if (cache != null) { + canvas.drawBitmap(cache, 0, 0, mLayerPaint); + caching = true; + } } - caching = true; } else { computeScroll(); @@ -11364,7 +11404,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mTransformationInfo.mRotation, mTransformationInfo.mRotationX, mTransformationInfo.mRotationY, mTransformationInfo.mScaleX, mTransformationInfo.mScaleY); - displayList.setCameraDistance(getCameraDistance()); + if (mTransformationInfo.mCamera == null) { + mTransformationInfo.mCamera = new Camera(); + mTransformationInfo.matrix3D = new Matrix(); + } + displayList.setCameraDistance(mTransformationInfo.mCamera.getLocationZ()); if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == PIVOT_EXPLICITLY_SET) { displayList.setPivotX(getPivotX()); displayList.setPivotY(getPivotY()); @@ -11458,8 +11502,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } else { switch (layerType) { case LAYER_TYPE_SOFTWARE: - buildDrawingCache(true); - cache = getDrawingCache(true); + if (useDisplayListProperties) { + hasDisplayList = canHaveDisplayList(); + } else { + buildDrawingCache(true); + cache = getDrawingCache(true); + } break; case LAYER_TYPE_HARDWARE: if (useDisplayListProperties) { @@ -11481,7 +11529,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal layerType != LAYER_TYPE_HARDWARE; int restoreTo = -1; - if (!useDisplayListProperties) { + if (!useDisplayListProperties || transformToApply != null) { restoreTo = canvas.save(); } if (offsetForScroll) { @@ -11515,11 +11563,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (concatMatrix) { // Undo the scroll translation, apply the transformation matrix, // then redo the scroll translate to get the correct result. - if (!useDisplayListProperties) { - canvas.translate(-transX, -transY); - canvas.concat(transformToApply.getMatrix()); - canvas.translate(transX, transY); - } + canvas.translate(-transX, -transY); + canvas.concat(transformToApply.getMatrix()); + canvas.translate(transX, transY); parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION; } @@ -11548,12 +11594,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal layerFlags |= Canvas.CLIP_TO_LAYER_SAVE_FLAG; } if (layerType == LAYER_TYPE_NONE) { - if (!useDisplayListProperties) { - final int scrollX = hasDisplayList ? 0 : sx; - final int scrollY = hasDisplayList ? 0 : sy; - canvas.saveLayerAlpha(scrollX, scrollY, scrollX + mRight - mLeft, - scrollY + mBottom - mTop, multipliedAlpha, layerFlags); - } + final int scrollX = hasDisplayList ? 0 : sx; + final int scrollY = hasDisplayList ? 0 : sy; + canvas.saveLayerAlpha(scrollX, scrollY, scrollX + mRight - mLeft, + scrollY + mBottom - mTop, multipliedAlpha, layerFlags); } } else { // Alpha is handled by the child directly, clobber the layer's alpha @@ -14487,8 +14531,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #TEXT_DIRECTION_RTL}, * {@link #TEXT_DIRECTION_LOCALE}, */ + @ViewDebug.ExportedProperty(category = "text", mapping = { + @ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_FIRST_STRONG, to = "FIRST_STRONG"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_ANY_RTL, to = "ANY_RTL"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_LTR, to = "LTR"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_RTL, to = "RTL"), + @ViewDebug.IntToString(from = TEXT_DIRECTION_LOCALE, to = "LOCALE") + }) public int getTextDirection() { - return mTextDirection; + return (mPrivateFlags2 & TEXT_DIRECTION_MASK) >> TEXT_DIRECTION_MASK_SHIFT; } /** @@ -14504,17 +14556,26 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #TEXT_DIRECTION_LOCALE}, */ public void setTextDirection(int textDirection) { - if (textDirection != mTextDirection) { - mTextDirection = textDirection; + if (getTextDirection() != textDirection) { + // Reset the current text direction and the resolved one + mPrivateFlags2 &= ~TEXT_DIRECTION_MASK; resetResolvedTextDirection(); + // Set the new text direction + mPrivateFlags2 |= ((textDirection << TEXT_DIRECTION_MASK_SHIFT) & TEXT_DIRECTION_MASK); requestLayout(); + invalidate(true); } } /** * Return the resolved text direction. * - * @return the resolved text direction. Return one of: + * This needs resolution if the value is TEXT_DIRECTION_INHERIT. The resolution matches + * {@link #getTextDirection()}if it is not TEXT_DIRECTION_INHERIT, otherwise resolution proceeds + * up the parent chain of the view. if there is no parent, then it will return the default + * {@link #TEXT_DIRECTION_FIRST_STRONG}. + * + * @return the resolved text direction. Returns one of: * * {@link #TEXT_DIRECTION_FIRST_STRONG} * {@link #TEXT_DIRECTION_ANY_RTL}, @@ -14523,10 +14584,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #TEXT_DIRECTION_LOCALE}, */ public int getResolvedTextDirection() { - if (mResolvedTextDirection == TEXT_DIRECTION_INHERIT) { + // The text direction will be resolved only if needed + if ((mPrivateFlags2 & TEXT_DIRECTION_RESOLVED) != TEXT_DIRECTION_RESOLVED) { resolveTextDirection(); } - return mResolvedTextDirection; + return (mPrivateFlags2 & TEXT_DIRECTION_RESOLVED_MASK) >> TEXT_DIRECTION_RESOLVED_MASK_SHIFT; } /** @@ -14534,17 +14596,51 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * resolution is done. */ public void resolveTextDirection() { - if (mResolvedTextDirection != TEXT_DIRECTION_INHERIT) { - // Resolution has already been done. - return; - } - if (mTextDirection != TEXT_DIRECTION_INHERIT) { - mResolvedTextDirection = mTextDirection; - } else if (mParent != null && mParent instanceof ViewGroup) { - mResolvedTextDirection = ((ViewGroup) mParent).getResolvedTextDirection(); - } else { - mResolvedTextDirection = TEXT_DIRECTION_FIRST_STRONG; + // Reset any previous text direction resolution + mPrivateFlags2 &= ~(TEXT_DIRECTION_RESOLVED | TEXT_DIRECTION_RESOLVED_MASK); + + // Set resolved text direction flag depending on text direction flag + final int textDirection = getTextDirection(); + switch(textDirection) { + case TEXT_DIRECTION_INHERIT: + if (canResolveTextDirection()) { + ViewGroup viewGroup = ((ViewGroup) mParent); + + // Set current resolved direction to the same value as the parent's one + final int parentResolvedDirection = viewGroup.getResolvedTextDirection(); + switch (parentResolvedDirection) { + case TEXT_DIRECTION_FIRST_STRONG: + case TEXT_DIRECTION_ANY_RTL: + case TEXT_DIRECTION_LTR: + case TEXT_DIRECTION_RTL: + case TEXT_DIRECTION_LOCALE: + mPrivateFlags2 |= + (parentResolvedDirection << TEXT_DIRECTION_RESOLVED_MASK_SHIFT); + break; + default: + // Default resolved direction is "first strong" heuristic + mPrivateFlags2 |= TEXT_DIRECTION_RESOLVED_DEFAULT; + } + } else { + // We cannot do the resolution if there is no parent, so use the default one + mPrivateFlags2 |= TEXT_DIRECTION_RESOLVED_DEFAULT; + } + break; + case TEXT_DIRECTION_FIRST_STRONG: + case TEXT_DIRECTION_ANY_RTL: + case TEXT_DIRECTION_LTR: + case TEXT_DIRECTION_RTL: + case TEXT_DIRECTION_LOCALE: + // Resolved direction is the same as text direction + mPrivateFlags2 |= (textDirection << TEXT_DIRECTION_RESOLVED_MASK_SHIFT); + break; + default: + // Default resolved direction is "first strong" heuristic + mPrivateFlags2 |= TEXT_DIRECTION_RESOLVED_DEFAULT; } + + // Set to resolved + mPrivateFlags2 |= TEXT_DIRECTION_RESOLVED; onResolvedTextDirectionChanged(); } @@ -14558,12 +14654,26 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Check if text direction resolution can be done. + * + * @return true if text direction resolution can be done otherwise return false. + */ + public boolean canResolveTextDirection() { + switch (getTextDirection()) { + case TEXT_DIRECTION_INHERIT: + return (mParent != null) && (mParent instanceof ViewGroup); + default: + return true; + } + } + + /** * Reset resolved text direction. Text direction can be resolved with a call to * getResolvedTextDirection(). Will call {@link View#onResolvedTextDirectionReset} when * reset is done. */ public void resetResolvedTextDirection() { - mResolvedTextDirection = TEXT_DIRECTION_INHERIT; + mPrivateFlags2 &= ~(TEXT_DIRECTION_RESOLVED | TEXT_DIRECTION_RESOLVED_MASK); onResolvedTextDirectionReset(); } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 42426b9..30d6ec7 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -1820,7 +1820,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * Resets the cancel next up flag. * Returns true if the flag was previously set. */ - private boolean resetCancelNextUpFlag(View view) { + private static boolean resetCancelNextUpFlag(View view) { if ((view.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { view.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; return true; @@ -2679,15 +2679,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager return child.draw(canvas, this, drawingTime); } - @Override - public void requestLayout() { - if (mChildrenCount > 0 && getAccessibilityNodeProvider() != null) { - throw new IllegalStateException("Views with AccessibilityNodeProvider" - + " can't have children."); - } - super.requestLayout(); - } - /** * * @param enabled True if children should be drawn with layers, false otherwise. @@ -3109,11 +3100,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) { - if (getAccessibilityNodeProvider() != null) { - throw new IllegalStateException("Views with AccessibilityNodeProvider" - + " can't have children."); - } - if (mTransition != null) { // Don't prevent other add transitions from completing, but cancel remove // transitions to let them complete the process before we add to the container @@ -3920,9 +3906,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager do { if (parent instanceof ViewGroup) { ViewGroup parentVG = (ViewGroup) parent; - parent = parentVG.invalidateChildInParentFast(left, top, dirty); - left = parentVG.mLeft; - top = parentVG.mTop; + if (parentVG.mLayerType != LAYER_TYPE_NONE) { + // Layered parents should be recreated, not just re-issued + parentVG.invalidate(); + parent = null; + } else { + parent = parentVG.invalidateChildInParentFast(left, top, dirty); + left = parentVG.mLeft; + top = parentVG.mTop; + } } else { // Reached the top; this calls into the usual invalidate method in // ViewRootImpl, which schedules a traversal @@ -4678,6 +4670,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager public void clearDisappearingChildren() { if (mDisappearingChildren != null) { mDisappearingChildren.clear(); + invalidate(); } } @@ -4789,7 +4782,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager view.mParent = null; } } - mGroupFlags |= FLAG_INVALIDATE_REQUIRED; + invalidate(); } } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 4eb70ab..befc1c6 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1809,7 +1809,7 @@ public final class ViewRootImpl implements ViewParent, * * @return The measure spec to use to measure the root view. */ - private int getRootMeasureSpec(int windowSize, int rootDimension) { + private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { @@ -1919,7 +1919,9 @@ public final class ViewRootImpl implements ViewParent, } private void performDraw() { - if (!mAttachInfo.mScreenOn) return; + if (!mAttachInfo.mScreenOn && !mReportNextDraw) { + return; + } final long drawStartTime; if (ViewDebug.DEBUG_LATENCY) { @@ -2430,12 +2432,12 @@ public final class ViewRootImpl implements ViewParent, mAccessibilityInteractionConnectionManager); removeSendWindowContentChangedCallback(); + destroyHardwareRenderer(); + mView = null; mAttachInfo.mRootView = null; mAttachInfo.mSurface = null; - destroyHardwareRenderer(); - mSurface.release(); if (mInputQueueCallback != null && mInputQueue != null) { @@ -2891,7 +2893,7 @@ public final class ViewRootImpl implements ViewParent, * @param focused The currently focused view. * @return An appropriate view, or null if no such view exists. */ - private ViewGroup findAncestorToTakeFocusInTouchMode(View focused) { + private static ViewGroup findAncestorToTakeFocusInTouchMode(View focused) { ViewParent parent = focused.getParent(); while (parent instanceof ViewGroup) { final ViewGroup vgParent = (ViewGroup) parent; @@ -3761,7 +3763,7 @@ public final class ViewRootImpl implements ViewParent, } } - private void getGfxInfo(View view, int[] info) { + private static void getGfxInfo(View view, int[] info) { DisplayList displayList = view.mDisplayList; info[0]++; if (displayList != null) { @@ -3782,6 +3784,7 @@ public final class ViewRootImpl implements ViewParent, if (immediate) { doDie(); } else { + destroyHardwareRenderer(); mHandler.sendEmptyMessage(MSG_DIE); } } @@ -3824,10 +3827,18 @@ public final class ViewRootImpl implements ViewParent, } private void destroyHardwareRenderer() { - if (mAttachInfo.mHardwareRenderer != null) { - mAttachInfo.mHardwareRenderer.destroy(true); - mAttachInfo.mHardwareRenderer = null; - mAttachInfo.mHardwareAccelerated = false; + AttachInfo attachInfo = mAttachInfo; + HardwareRenderer hardwareRenderer = attachInfo.mHardwareRenderer; + + if (hardwareRenderer != null) { + if (mView != null) { + hardwareRenderer.destroyHardwareResources(mView); + } + hardwareRenderer.destroy(true); + hardwareRenderer.setRequested(false); + + attachInfo.mHardwareRenderer = null; + attachInfo.mHardwareAccelerated = false; } } diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 0e4a30f..f2ee9f9 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -354,7 +354,7 @@ public class WindowManagerImpl implements WindowManager { View removeViewLocked(int index) { ViewRootImpl root = mRoots[index]; View view = root.getView(); - + // Don't really remove until we have matched all calls to add(). root.mAddNesting--; if (root.mAddNesting > 0) { diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java index dc8c71b..d92ebcd 100644 --- a/core/java/android/view/animation/Animation.java +++ b/core/java/android/view/animation/Animation.java @@ -875,7 +875,7 @@ public abstract class Animation implements Cloneable { * otherwise. * * @param currentTime Where we are in the animation. This is wall clock time. - * @param outTransformation A tranformation object that is provided by the + * @param outTransformation A transformation object that is provided by the * caller and will be filled in by the animation. * @param scale Scaling factor to apply to any inputs to the transform operation, such * pivot points being rotated or scaled around. diff --git a/core/java/android/view/animation/Transformation.java b/core/java/android/view/animation/Transformation.java index cf210c8..e8c1d23 100644 --- a/core/java/android/view/animation/Transformation.java +++ b/core/java/android/view/animation/Transformation.java @@ -112,6 +112,16 @@ public class Transformation { } /** + * Like {@link #compose(Transformation)} but does this.postConcat(t) of + * the transformation matrix. + * @hide + */ + public void postCompose(Transformation t) { + mAlpha *= t.getAlpha(); + mMatrix.postConcat(t.getMatrix()); + } + + /** * @return The 3x3 Matrix representing the trnasformation to apply to the * coordinates of the object being animated */ diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 0e5ff20..17dbde8 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -403,6 +403,9 @@ public final class InputMethodManager { mIInputContext.finishComposingText(); } catch (RemoteException e) { } + // Check focus again in case that "onWindowFocus" is called before + // handling this message. + checkFocus(mHasBeenInactive); } } return; @@ -1173,13 +1176,17 @@ public final class InputMethodManager { } } + private void checkFocus(boolean forceNewFocus) { + if (checkFocusNoStartInput(forceNewFocus)) { + startInputInner(null, 0, 0, 0); + } + } + /** * @hide */ public void checkFocus() { - if (checkFocusNoStartInput(false)) { - startInputInner(null, 0, 0, 0); - } + checkFocus(false); } private boolean checkFocusNoStartInput(boolean forceNewFocus) { diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java index 18dec52..c22750e 100644 --- a/core/java/android/view/inputmethod/InputMethodSubtype.java +++ b/core/java/android/view/inputmethod/InputMethodSubtype.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.IllegalFormatException; import java.util.List; import java.util.Locale; @@ -45,6 +46,9 @@ public final class InputMethodSubtype implements Parcelable { private static final String TAG = InputMethodSubtype.class.getSimpleName(); private static final String EXTRA_VALUE_PAIR_SEPARATOR = ","; private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "="; + // TODO: remove this + private static final String EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME = + "UntranslatableReplacementStringInSubtypeName"; private final boolean mIsAuxiliary; private final boolean mOverridesImplicitlyEnabledSubtype; @@ -215,7 +219,17 @@ public final class InputMethodSubtype implements Parcelable { final CharSequence subtypeName = context.getPackageManager().getText( packageName, mSubtypeNameResId, appInfo); if (!TextUtils.isEmpty(subtypeName)) { - return String.format(subtypeName.toString(), localeStr); + final String replacementString = + containsExtraValueKey(EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME) + ? getExtraValueOf(EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME) + : localeStr; + try { + return String.format( + subtypeName.toString(), replacementString != null ? replacementString : ""); + } catch (IllegalFormatException e) { + Slog.w(TAG, "Found illegal format in subtype name("+ subtypeName + "): " + e); + return ""; + } } else { return localeStr; } diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java index dbcea71..72af251 100644 --- a/core/java/android/webkit/BrowserFrame.java +++ b/core/java/android/webkit/BrowserFrame.java @@ -448,7 +448,7 @@ class BrowserFrame extends Handler { // loadType is not used yet if (isMainFrame) { mCommitted = true; - mWebViewCore.getWebView().mViewManager.postResetStateAll(); + mWebViewCore.getWebViewClassic().mViewManager.postResetStateAll(); } } @@ -910,7 +910,7 @@ class BrowserFrame extends Handler { * Close this frame and window. */ private void closeWindow(WebViewCore w) { - mCallbackProxy.onCloseWindow(w.getWebView()); + mCallbackProxy.onCloseWindow(w.getWebViewClassic()); } // XXX: Must match PolicyAction in FrameLoaderTypes.h in webcore diff --git a/core/java/android/webkit/DebugFlags.java b/core/java/android/webkit/DebugFlags.java index a21d3ee..349113e 100644 --- a/core/java/android/webkit/DebugFlags.java +++ b/core/java/android/webkit/DebugFlags.java @@ -42,12 +42,7 @@ class DebugFlags { public static final boolean WEB_BACK_FORWARD_LIST = false; public static final boolean WEB_SETTINGS = false; public static final boolean WEB_SYNC_MANAGER = false; - public static final boolean WEB_TEXT_VIEW = false; public static final boolean WEB_VIEW = false; public static final boolean WEB_VIEW_CORE = false; - /* - * Set to true to allow the WebTextView to draw on top of the web page in a - * different color with no background so you can see how the two line up. - */ - public static final boolean DRAW_WEBTEXTVIEW = false; + public static final boolean MEASURE_PAGE_SWAP_FPS = false; } diff --git a/core/java/android/webkit/FindListener.java b/core/java/android/webkit/FindListener.java deleted file mode 100644 index 124f737..0000000 --- a/core/java/android/webkit/FindListener.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2012 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.webkit; - -/** - * @hide - */ -public interface FindListener { - /** - * Notify the host application that a find result is available. - * - * @param numberOfMatches How many matches have been found - * @param activeMatchOrdinal The ordinal of the currently selected match - * @param isDoneCounting Whether we have finished counting matches - */ - public void onFindResultReceived(int numberOfMatches, - int activeMatchOrdinal, boolean isDoneCounting); -} diff --git a/core/java/android/webkit/HTML5Audio.java b/core/java/android/webkit/HTML5Audio.java index 8e1f573..689884f 100644 --- a/core/java/android/webkit/HTML5Audio.java +++ b/core/java/android/webkit/HTML5Audio.java @@ -183,7 +183,7 @@ class HTML5Audio extends Handler resetMediaPlayer(); mContext = webViewCore.getContext(); mIsPrivateBrowsingEnabledGetter = new IsPrivateBrowsingEnabledGetter( - webViewCore.getContext().getMainLooper(), webViewCore.getWebView()); + webViewCore.getContext().getMainLooper(), webViewCore.getWebViewClassic()); } private void resetMediaPlayer() { diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java index 40c3778..5fa4bad 100644 --- a/core/java/android/webkit/HTML5VideoViewProxy.java +++ b/core/java/android/webkit/HTML5VideoViewProxy.java @@ -724,7 +724,7 @@ class HTML5VideoViewProxy extends Handler * @return a new HTML5VideoViewProxy object. */ public static HTML5VideoViewProxy getInstance(WebViewCore webViewCore, int nativePtr) { - return new HTML5VideoViewProxy(webViewCore.getWebView(), nativePtr); + return new HTML5VideoViewProxy(webViewCore.getWebViewClassic(), nativePtr); } /* package */ WebViewClassic getWebView() { diff --git a/core/java/android/webkit/WebSettingsClassic.java b/core/java/android/webkit/WebSettingsClassic.java index 6850eea..c41bc00 100644 --- a/core/java/android/webkit/WebSettingsClassic.java +++ b/core/java/android/webkit/WebSettingsClassic.java @@ -121,9 +121,6 @@ public class WebSettingsClassic extends WebSettings { private boolean mForceUserScalable = false; // AutoFill Profile data - /** - * @hide for now, pending API council approval. - */ public static class AutoFillProfile { private int mUniqueId; private String mFullName; @@ -644,7 +641,6 @@ public class WebSettingsClassic extends WebSettings { /** * Set the double-tap zoom of the page in percent. Default is 100. * @param doubleTapZoom A percent value for increasing or decreasing the double-tap zoom. - * @hide */ public void setDoubleTapZoom(int doubleTapZoom) { if (mDoubleTapZoom != doubleTapZoom) { @@ -656,7 +652,6 @@ public class WebSettingsClassic extends WebSettings { /** * Get the double-tap zoom of the page in percent. * @return A percent value describing the double-tap zoom. - * @hide */ public int getDoubleTapZoom() { return mDoubleTapZoom; @@ -1012,7 +1007,6 @@ public class WebSettingsClassic extends WebSettings { /** * Set the number of pages cached by the WebKit for the history navigation. * @param size A non-negative integer between 0 (no cache) and 20 (max). - * @hide */ public synchronized void setPageCacheCapacity(int size) { if (size < 0) size = 0; @@ -1108,7 +1102,6 @@ public class WebSettingsClassic extends WebSettings { /** * Tell the WebView to use Skia's hardware accelerated rendering path * @param flag True if the WebView should use Skia's hw-accel path - * @hide */ public synchronized void setHardwareAccelSkiaEnabled(boolean flag) { if (mHardwareAccelSkia != flag) { @@ -1119,7 +1112,6 @@ public class WebSettingsClassic extends WebSettings { /** * @return True if the WebView is using hardware accelerated skia - * @hide */ public synchronized boolean getHardwareAccelSkiaEnabled() { return mHardwareAccelSkia; @@ -1128,7 +1120,6 @@ public class WebSettingsClassic extends WebSettings { /** * Tell the WebView to show the visual indicator * @param flag True if the WebView should show the visual indicator - * @hide */ public synchronized void setShowVisualIndicator(boolean flag) { if (mShowVisualIndicator != flag) { @@ -1139,7 +1130,6 @@ public class WebSettingsClassic extends WebSettings { /** * @return True if the WebView is showing the visual indicator - * @hide */ public synchronized boolean getShowVisualIndicator() { return mShowVisualIndicator; @@ -1283,7 +1273,6 @@ public class WebSettingsClassic extends WebSettings { * @param flag True if the WebView should enable WebWorkers. * Note that this flag only affects V8. JSC does not have * an equivalent setting. - * @hide */ public synchronized void setWorkersEnabled(boolean flag) { if (mWorkersEnabled != flag) { @@ -1305,8 +1294,8 @@ public class WebSettingsClassic extends WebSettings { /** * Sets whether XSS Auditor is enabled. + * Only used by LayoutTestController. * @param flag Whether XSS Auditor should be enabled. - * @hide Only used by LayoutTestController. */ public synchronized void setXSSAuditorEnabled(boolean flag) { if (mXSSAuditorEnabled != flag) { @@ -1507,7 +1496,6 @@ public class WebSettingsClassic extends WebSettings { * of an HTML page to fit the screen. This conflicts with attempts by * the UI to zoom in and out of an image, so it is set false by default. * @param shrink Set true to let webkit shrink the standalone image to fit. - * {@hide} */ public void setShrinksStandaloneImagesToFit(boolean shrink) { if (mShrinksStandaloneImagesToFit != shrink) { @@ -1520,7 +1508,6 @@ public class WebSettingsClassic extends WebSettings { * Specify the maximum decoded image size. The default is * 2 megs for small memory devices and 8 megs for large memory devices. * @param size The maximum decoded size, or zero to set to the default. - * @hide */ public void setMaximumDecodedImageSize(long size) { if (mMaximumDecodedImageSize != size) { @@ -1562,7 +1549,6 @@ public class WebSettingsClassic extends WebSettings { /** * Returns whether the viewport metatag can disable zooming - * @hide */ public boolean forceUserScalable() { return mForceUserScalable; @@ -1571,7 +1557,6 @@ public class WebSettingsClassic extends WebSettings { /** * Sets whether viewport metatag can disable zooming. * @param flag Whether or not to forceably enable user scalable. - * @hide */ public synchronized void setForceUserScalable(boolean flag) { mForceUserScalable = flag; @@ -1584,9 +1569,6 @@ public class WebSettingsClassic extends WebSettings { } } - /** - * @hide - */ public synchronized void setAutoFillEnabled(boolean enabled) { // AutoFill is always disabled in private browsing mode. boolean autoFillEnabled = enabled && !mPrivateBrowsingEnabled; @@ -1596,16 +1578,10 @@ public class WebSettingsClassic extends WebSettings { } } - /** - * @hide - */ public synchronized boolean getAutoFillEnabled() { return mAutoFillEnabled; } - /** - * @hide - */ public synchronized void setAutoFillProfile(AutoFillProfile profile) { if (mAutoFillProfile != profile) { mAutoFillProfile = profile; @@ -1613,9 +1589,6 @@ public class WebSettingsClassic extends WebSettings { } } - /** - * @hide - */ public synchronized AutoFillProfile getAutoFillProfile() { return mAutoFillProfile; } @@ -1633,18 +1606,12 @@ public class WebSettingsClassic extends WebSettings { } } - /** - * @hide - */ public void setProperty(String key, String value) { if (mWebView.nativeSetProperty(key, value)) { - mWebView.contentInvalidateAll(); + mWebView.invalidate(); } } - /** - * @hide - */ public String getProperty(String key) { return mWebView.nativeGetProperty(key); } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 5e09416..d225594 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.Picture; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -311,6 +312,24 @@ public class WebView extends AbsoluteLayout public static final String SCHEME_GEO = "geo:0,0?q="; /** + * Interface to listen for find results. + * @hide + */ + public interface FindListener { + /** + * Notify the listener about progress made by a find operation. + * + * @param numberOfMatches How many matches have been found. + * @param activeMatchOrdinal The zero-based ordinal of the currently selected match. + * @param isDoneCounting Whether the find operation has actually completed. The listener + * may be notified multiple times while the operation is underway, and the numberOfMatches + * value should not be considered final unless isDoneCounting is true. + */ + public void onFindResultReceived(int numberOfMatches, int activeMatchOrdinal, + boolean isDoneCounting); + } + + /** * Interface to listen for new pictures as they change. * @deprecated This interface is now obsolete. */ @@ -1227,10 +1246,10 @@ public class WebView extends AbsoluteLayout } /** - * Register the interface to be used when a find-on-page result has become - * available. This will replace the current handler. + * Register the listener to be notified as find-on-page operations progress. + * This will replace the current listener. * - * @param listener An implementation of FindListener + * @param listener An implementation of {@link WebView#FindListener}. * @hide */ public void setFindListener(FindListener listener) { @@ -1980,4 +1999,10 @@ public class WebView extends AbsoluteLayout public void setBackgroundColor(int color) { mProvider.getViewDelegate().setBackgroundColor(color); } + + @Override + public void setLayerType(int layerType, Paint paint) { + super.setLayerType(layerType, paint); + mProvider.getViewDelegate().setLayerType(layerType, paint); + } } diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java index e5434ce..5ae2fe0 100644 --- a/core/java/android/webkit/WebViewClassic.java +++ b/core/java/android/webkit/WebViewClassic.java @@ -348,8 +348,6 @@ import java.util.regex.Pattern; * * @hide */ -// TODO: Remove duplicated API documentation and @hide from fields and methods, and -// checkThread() call. (All left in for now to ease branch merging.) // TODO: Check if any WebView published API methods are called from within here, and if so // we should bounce the call out via the proxy to enable any sub-class to override it. @Widget @@ -1255,6 +1253,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc static final int AUTOFILL_FORM = 148; static final int ANIMATE_TEXT_SCROLL = 149; static final int EDIT_TEXT_SIZE_CHANGED = 150; + static final int SHOW_CARET_HANDLE = 151; private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID; private static final int LAST_PACKAGE_MSG_ID = HIT_TEST_RESULT; @@ -1441,7 +1440,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private PictureListener mPictureListener; // Used to notify listeners about find-on-page results. - private FindListener mFindListener; + private WebView.FindListener mFindListener; /** * Refer to {@link WebView#requestFocusNodeHref(Message)} for more information @@ -1463,8 +1462,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void init(Map<String, Object> javaScriptInterfaces, boolean privateBrowsing) { - checkThread(); - Context context = mContext; // Used by the chrome stack to find application paths @@ -1496,8 +1493,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mEditTextScroller = new Scroller(context); } - // === START: WebView Proxy binding === - // Keep the webview proxy / SPI related stuff in this section, to minimize merge conflicts. + // WebViewProvider bindings static class Factory implements WebViewFactoryProvider, WebViewFactoryProvider.Statics { @Override @@ -1586,8 +1582,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mWebViewPrivate.setScrollYRaw(mScrollY); } - // === END: WebView Proxy binding === - private static class TrustStorageListener extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -1968,7 +1962,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void setHorizontalScrollbarOverlay(boolean overlay) { - checkThread(); mOverlayHorizontalScrollbar = overlay; } @@ -1977,7 +1970,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void setVerticalScrollbarOverlay(boolean overlay) { - checkThread(); mOverlayVerticalScrollbar = overlay; } @@ -1986,7 +1978,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public boolean overlayHorizontalScrollbar() { - checkThread(); return mOverlayHorizontalScrollbar; } @@ -1995,7 +1986,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public boolean overlayVerticalScrollbar() { - checkThread(); return mOverlayVerticalScrollbar; } @@ -2021,7 +2011,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc /** * Returns the height (in pixels) of the embedded title bar (if any). Does not care about * scrolling - * @hide */ protected int getTitleHeight() { if (mWebView instanceof TitleBarDelegate) { @@ -2038,7 +2027,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc public int getVisibleTitleHeight() { // Actually, this method returns the height of the embedded title bar if one is set via the // hidden setEmbeddedTitleBar method. - checkThread(); return getVisibleTitleHeightImpl(); } @@ -2084,7 +2072,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public SslCertificate getCertificate() { - checkThread(); return mCertificate; } @@ -2093,7 +2080,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void setCertificate(SslCertificate certificate) { - checkThread(); if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "setCertificate=" + certificate); } @@ -2110,7 +2096,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void savePassword(String host, String username, String password) { - checkThread(); mDatabase.setUsernamePassword(host, username, password); } @@ -2120,7 +2105,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc @Override public void setHttpAuthUsernamePassword(String host, String realm, String username, String password) { - checkThread(); mDatabase.setHttpAuthUsernamePassword(host, realm, username, password); } @@ -2129,7 +2113,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public String[] getHttpAuthUsernamePassword(String host, String realm) { - checkThread(); return mDatabase.getHttpAuthUsernamePassword(host, realm); } @@ -2169,7 +2152,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void destroy() { - checkThread(); destroyImpl(); } @@ -2202,7 +2184,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Deprecated public static void enablePlatformNotifications() { - checkThread(); synchronized (WebViewClassic.class) { sNotificationsEnabled = true; Context context = JniUtil.getContext(); @@ -2216,7 +2197,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Deprecated public static void disablePlatformNotifications() { - checkThread(); synchronized (WebViewClassic.class) { sNotificationsEnabled = false; Context context = JniUtil.getContext(); @@ -2230,10 +2210,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * * @param flags JS engine flags in a String * - * @hide This is an implementation detail. + * This is an implementation detail. */ public void setJsFlags(String flags) { - checkThread(); mWebViewCore.sendMessage(EventHub.SET_JS_FLAGS, flags); } @@ -2242,17 +2221,14 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void setNetworkAvailable(boolean networkUp) { - checkThread(); mWebViewCore.sendMessage(EventHub.SET_NETWORK_STATE, networkUp ? 1 : 0, 0); } /** * Inform WebView about the current network type. - * {@hide} */ public void setNetworkType(String type, String subtype) { - checkThread(); Map<String, String> map = new HashMap<String, String>(); map.put("type", type); map.put("subtype", subtype); @@ -2264,7 +2240,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public WebBackForwardList saveState(Bundle outState) { - checkThread(); if (outState == null) { return null; } @@ -2316,7 +2291,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc @Override @Deprecated public boolean savePicture(Bundle b, final File dest) { - checkThread(); if (dest == null || b == null) { return false; } @@ -2378,7 +2352,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc @Override @Deprecated public boolean restorePicture(Bundle b, File src) { - checkThread(); if (src == null || b == null) { return false; } @@ -2424,7 +2397,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * of WebView. * @param stream The {@link OutputStream} to save to * @return True if saved successfully - * @hide */ public boolean saveViewState(OutputStream stream) { try { @@ -2440,7 +2412,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * {@link #saveViewState(OutputStream)} for more information. * @param stream The {@link InputStream} to load from * @return True if loaded successfully - * @hide */ public boolean loadViewState(InputStream stream) { try { @@ -2470,7 +2441,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public WebBackForwardList restoreState(Bundle inState) { - checkThread(); WebBackForwardList returnList = null; if (inState == null) { return returnList; @@ -2527,7 +2497,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void loadUrl(String url, Map<String, String> additionalHttpHeaders) { - checkThread(); loadUrlImpl(url, additionalHttpHeaders); } @@ -2545,7 +2514,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void loadUrl(String url) { - checkThread(); loadUrlImpl(url); } @@ -2561,7 +2529,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void postUrl(String url, byte[] postData) { - checkThread(); if (URLUtil.isNetworkUrl(url)) { switchOutDrawHistory(); WebViewCore.PostUrlData arg = new WebViewCore.PostUrlData(); @@ -2579,7 +2546,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void loadData(String data, String mimeType, String encoding) { - checkThread(); loadDataImpl(data, mimeType, encoding); } @@ -2600,7 +2566,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc @Override public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl) { - checkThread(); if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) { loadDataImpl(data, mimeType, encoding); @@ -2622,7 +2587,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void saveWebArchive(String filename) { - checkThread(); saveWebArchiveImpl(filename, false, null); } @@ -2644,7 +2608,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback) { - checkThread(); saveWebArchiveImpl(basename, autoname, callback); } @@ -2659,7 +2622,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void stopLoading() { - checkThread(); // TODO: should we clear all the messages in the queue before sending // STOP_LOADING? switchOutDrawHistory(); @@ -2671,7 +2633,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void reload() { - checkThread(); clearHelpers(); switchOutDrawHistory(); mWebViewCore.sendMessage(EventHub.RELOAD); @@ -2682,7 +2643,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public boolean canGoBack() { - checkThread(); WebBackForwardList l = mCallbackProxy.getBackForwardList(); synchronized (l) { if (l.getClearPending()) { @@ -2698,7 +2658,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void goBack() { - checkThread(); goBackOrForwardImpl(-1); } @@ -2707,7 +2666,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public boolean canGoForward() { - checkThread(); WebBackForwardList l = mCallbackProxy.getBackForwardList(); synchronized (l) { if (l.getClearPending()) { @@ -2723,7 +2681,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void goForward() { - checkThread(); goBackOrForwardImpl(1); } @@ -2732,7 +2689,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public boolean canGoBackOrForward(int steps) { - checkThread(); WebBackForwardList l = mCallbackProxy.getBackForwardList(); synchronized (l) { if (l.getClearPending()) { @@ -2749,7 +2705,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void goBackOrForward(int steps) { - checkThread(); goBackOrForwardImpl(steps); } @@ -2770,7 +2725,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public boolean isPrivateBrowsingEnabled() { - checkThread(); return getSettings().isPrivateBrowsingEnabled(); } @@ -2792,7 +2746,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public boolean pageUp(boolean top) { - checkThread(); if (mNativeClass == 0) { return false; } @@ -2817,7 +2770,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public boolean pageDown(boolean bottom) { - checkThread(); if (mNativeClass == 0) { return false; } @@ -2841,7 +2793,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void clearView() { - checkThread(); mContentWidth = 0; mContentHeight = 0; setBaseLayer(0, null, false, false); @@ -2853,7 +2804,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public Picture capturePicture() { - checkThread(); if (mNativeClass == 0) return null; Picture result = new Picture(); nativeCopyBaseContentToPicture(result); @@ -2865,7 +2815,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public float getScale() { - checkThread(); return mZoomManager.getScale(); } @@ -2883,7 +2832,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void setInitialScale(int scaleInPercent) { - checkThread(); mZoomManager.setInitialScaleInPercent(scaleInPercent); } @@ -2892,7 +2840,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void invokeZoomPicker() { - checkThread(); if (!getSettings().supportZoom()) { Log.w(LOGTAG, "This WebView doesn't support zoom."); return; @@ -2906,7 +2853,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public HitTestResult getHitTestResult() { - checkThread(); return mInitialHitTestResult; } @@ -2942,7 +2888,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void requestFocusNodeHref(Message hrefMsg) { - checkThread(); if (hrefMsg == null) { return; } @@ -2965,7 +2910,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void requestImageRef(Message msg) { - checkThread(); if (0 == mNativeClass) return; // client isn't initialized String url = mFocusedNode != null ? mFocusedNode.mImageUrl : null; Bundle data = msg.getData(); @@ -3019,7 +2963,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * along with it vertically, while remaining in view horizontally. Pass * null to remove the title bar from the WebView, and return to drawing * the WebView normally without translating to account for the title bar. - * @hide */ public void setEmbeddedTitleBar(View v) { if (mWebView instanceof TitleBarDelegate) { @@ -3041,7 +2984,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * Set where to render the embedded title bar * NO_GRAVITY at the top of the page * TOP at the top of the screen - * @hide */ public void setTitleBarGravity(int gravity) { mTitleGravity = gravity; @@ -3421,7 +3363,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc return getViewHeight(); } - /** @hide */ @Override public void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar, @@ -3470,7 +3411,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public String getUrl() { - checkThread(); WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); return h != null ? h.getUrl() : null; } @@ -3480,7 +3420,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public String getOriginalUrl() { - checkThread(); WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); return h != null ? h.getOriginalUrl() : null; } @@ -3490,7 +3429,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public String getTitle() { - checkThread(); WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); return h != null ? h.getTitle() : null; } @@ -3500,7 +3438,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public Bitmap getFavicon() { - checkThread(); WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); return h != null ? h.getFavicon() : null; } @@ -3519,7 +3456,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public int getProgress() { - checkThread(); return mCallbackProxy.getProgress(); } @@ -3528,7 +3464,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public int getContentHeight() { - checkThread(); return mContentHeight; } @@ -3540,9 +3475,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc return mContentWidth; } - /** - * @hide - */ public int getPageBackgroundColor() { return nativeGetBackgroundColor(); } @@ -3552,7 +3484,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void pauseTimers() { - checkThread(); mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS); } @@ -3561,7 +3492,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void resumeTimers() { - checkThread(); mWebViewCore.sendMessage(EventHub.RESUME_TIMERS); } @@ -3570,7 +3500,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void onPause() { - checkThread(); if (!mIsPaused) { mIsPaused = true; mWebViewCore.sendMessage(EventHub.ON_PAUSE); @@ -3609,7 +3538,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void onResume() { - checkThread(); if (mIsPaused) { mIsPaused = false; mWebViewCore.sendMessage(EventHub.ON_RESUME); @@ -3641,7 +3569,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void freeMemory() { - checkThread(); mWebViewCore.sendMessage(EventHub.FREE_MEMORY); } @@ -3650,7 +3577,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void clearCache(boolean includeDiskFiles) { - checkThread(); // Note: this really needs to be a static method as it clears cache for all // WebView. But we need mWebViewCore to send message to WebCore thread, so // we can't make this static. @@ -3663,7 +3589,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void clearFormData() { - checkThread(); if (mAutoCompletePopup != null) { mAutoCompletePopup.clearAdapter(); } @@ -3674,7 +3599,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void clearHistory() { - checkThread(); mCallbackProxy.getBackForwardList().setClearPending(); mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY); } @@ -3684,7 +3608,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void clearSslPreferences() { - checkThread(); mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE); } @@ -3693,18 +3616,14 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public WebBackForwardList copyBackForwardList() { - checkThread(); return mCallbackProxy.getBackForwardList().clone(); } /** - * Register the interface to be used when a find-on-page result has become - * available. This will replace the current handler. - * - * @param listener An implementation of FindListener + * See {@link WebView#setFindListener(WebView.FindListener)}. + * @hide */ - public void setFindListener(FindListener listener) { - checkThread(); + public void setFindListener(WebView.FindListener listener) { mFindListener = listener; } @@ -3713,7 +3632,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void findNext(boolean forward) { - checkThread(); if (0 == mNativeClass) return; // client isn't initialized mWebViewCore.sendMessage(EventHub.FIND_NEXT, forward ? 1 : 0); } @@ -3726,15 +3644,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc return findAllBody(find, false); } - /** - * @hide - */ public void findAllAsync(String find) { findAllBody(find, true); } private int findAllBody(String find, boolean isAsync) { - checkThread(); if (0 == mNativeClass) return 0; // client isn't initialized mLastFind = find; if (find == null) return 0; @@ -3771,7 +3685,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * @return boolean True if the find dialog is shown, false otherwise. */ public boolean showFindDialog(String text, boolean showIme) { - checkThread(); FindActionModeCallback callback = new FindActionModeCallback(mContext); if (mWebView.getParent() == null || mWebView.startActionMode(callback) == null) { // Could not start the action mode, so end Find on page @@ -3840,12 +3753,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * @return the address, or if no address is found, return null. */ public static String findAddress(String addr) { - checkThread(); return findAddress(addr, false); } /** - * @hide * Return the first substring consisting of the address of a physical * location. Currently, only addresses in the United States are detected, * and consist of: @@ -3875,7 +3786,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void clearMatches() { - checkThread(); if (mNativeClass == 0) return; mWebViewCore.removeMessages(EventHub.FIND_ALL); @@ -3905,7 +3815,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void documentHasImages(Message response) { - checkThread(); if (response == null) { return; } @@ -3914,8 +3823,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc /** * Request the scroller to abort any ongoing animation - * - * @hide */ public void stopScroll() { mScroller.forceFinished(true); @@ -4339,7 +4246,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void setWebViewClient(WebViewClient client) { - checkThread(); mCallbackProxy.setWebViewClient(client); } @@ -4347,7 +4253,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * Gets the WebViewClient * @return the current WebViewClient instance. * - * @hide This is an implementation detail. + * This is an implementation detail. */ public WebViewClient getWebViewClient() { return mCallbackProxy.getWebViewClient(); @@ -4358,7 +4264,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void setDownloadListener(DownloadListener listener) { - checkThread(); mCallbackProxy.setDownloadListener(listener); } @@ -4367,7 +4272,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void setWebChromeClient(WebChromeClient client) { - checkThread(); mCallbackProxy.setWebChromeClient(client); } @@ -4375,7 +4279,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * Gets the chrome handler. * @return the current WebChromeClient instance. * - * @hide This is an implementation detail. + * This is an implementation detail. */ public WebChromeClient getWebChromeClient() { return mCallbackProxy.getWebChromeClient(); @@ -4386,7 +4290,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * WebBackForwardListClient for handling new items and changes in the * history index. * @param client An implementation of WebBackForwardListClient. - * {@hide} */ public void setWebBackForwardListClient(WebBackForwardListClient client) { mCallbackProxy.setWebBackForwardListClient(client); @@ -4394,7 +4297,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc /** * Gets the WebBackForwardListClient. - * {@hide} */ public WebBackForwardListClient getWebBackForwardListClient() { return mCallbackProxy.getWebBackForwardListClient(); @@ -4406,21 +4308,14 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc @Override @Deprecated public void setPictureListener(PictureListener listener) { - checkThread(); mPictureListener = listener; } - /** - * {@hide} - */ /* FIXME: Debug only! Remove for SDK! */ public void externalRepresentation(Message callback) { mWebViewCore.sendMessage(EventHub.REQUEST_EXT_REPRESENTATION, callback); } - /** - * {@hide} - */ /* FIXME: Debug only! Remove for SDK! */ public void documentAsText(Message callback) { mWebViewCore.sendMessage(EventHub.REQUEST_DOC_AS_TEXT, callback); @@ -4431,7 +4326,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void addJavascriptInterface(Object object, String name) { - checkThread(); if (object == null) { return; } @@ -4446,7 +4340,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public void removeJavascriptInterface(String interfaceName) { - checkThread(); if (mWebViewCore != null) { WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData(); arg.mInterfaceName = interfaceName; @@ -4461,7 +4354,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public WebSettingsClassic getSettings() { - checkThread(); return (mWebViewCore != null) ? mWebViewCore.getSettings() : null; } @@ -4470,7 +4362,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Deprecated public static synchronized PluginList getPluginList() { - checkThread(); return new PluginList(); } @@ -4479,7 +4370,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Deprecated public void refreshPlugins(boolean reloadOpenPages) { - checkThread(); } //------------------------------------------------------------------------- @@ -4783,7 +4673,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc /** * Select the word at the last click point. * - * @hide This is an implementation detail. + * This is an implementation detail. */ public boolean selectText() { int x = viewToContentX(mLastTouchX + getScrollX()); @@ -4827,10 +4717,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc queueFull = nativeSetBaseLayer(mNativeClass, layer, invalRegion, showVisualIndicator, isPictureAfterFirstLayout); - if (layer == 0 || isPictureAfterFirstLayout) { - mWebViewCore.resumeWebKitDraw(); - } else if (queueFull) { + if (queueFull) { mWebViewCore.pauseWebKitDraw(); + } else { + mWebViewCore.resumeWebKitDraw(); } if (mHTML5VideoViewProxy != null) { @@ -4923,7 +4813,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * startX, startY, endX, endY */ private void getSelectionHandles(int[] handles) { - handles[0] = mSelectCursorBase.right; + handles[0] = mSelectCursorBase.left; handles[1] = mSelectCursorBase.bottom; handles[2] = mSelectCursorExtent.left; handles[3] = mSelectCursorExtent.bottom; @@ -5134,7 +5024,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc /** * Dump the display tree to "/sdcard/displayTree.txt" * - * @hide debug only + * debug only */ public void dumpDisplayTree() { nativeDumpDisplayTree(getUrl()); @@ -5144,7 +5034,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * Dump the dom tree to adb shell if "toFile" is False, otherwise dump it to * "/sdcard/domTree.txt" * - * @hide debug only + * debug only */ public void dumpDomTree(boolean toFile) { mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE, toFile ? 1 : 0, 0); @@ -5154,7 +5044,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * Dump the render tree to adb shell if "toFile" is False, otherwise dump it * to "/sdcard/renderTree.txt" * - * @hide debug only + * debug only */ public void dumpRenderTree(boolean toFile) { mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE, toFile ? 1 : 0, 0); @@ -5163,7 +5053,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc /** * Called by DRT on UI thread, need to proxy to WebCore thread. * - * @hide debug only + * debug only */ public void useMockDeviceOrientation() { mWebViewCore.sendMessage(EventHub.USE_MOCK_DEVICE_ORIENTATION); @@ -5172,7 +5062,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc /** * Called by DRT on WebCore thread. * - * @hide debug only + * debug only */ public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha, boolean canProvideBeta, double beta, boolean canProvideGamma, double gamma) { @@ -5468,9 +5358,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private boolean setupWebkitSelect() { syncSelectionCursors(); - if (mIsCaretSelection) { - showPasteWindow(); - } else if (!startSelectActionMode()) { + if (!mIsCaretSelection && !startSelectActionMode()) { selectionDone(); return false; } @@ -5511,13 +5399,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc @Override @Deprecated public void emulateShiftHeld() { - checkThread(); } /** * Select all of the text in this WebView. * - * @hide This is an implementation detail. + * This is an implementation detail. */ public void selectAll() { mWebViewCore.sendMessage(EventHub.SELECT_ALL); @@ -5539,7 +5426,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc if (!mIsCaretSelection) { updateWebkitSelection(); } - mIsCaretSelection = false; invalidate(); // redraw without selection mAutoScrollX = 0; mAutoScrollY = 0; @@ -5550,7 +5436,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc /** * Copy the selection to the clipboard * - * @hide This is an implementation detail. + * This is an implementation detail. */ public boolean copySelection() { boolean copiedSomething = false; @@ -5577,7 +5463,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc /** * Cut the selected text into the clipboard * - * @hide This is an implementation detail + * This is an implementation detail */ public void cutSelection() { copySelection(); @@ -5589,7 +5475,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc /** * Paste text from the clipboard to the cursor position. * - * @hide This is an implementation detail + * This is an implementation detail */ public void pasteFromClipboard() { ClipboardManager cm = (ClipboardManager)mContext @@ -5605,7 +5491,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } /** - * @hide This is an implementation detail. + * This is an implementation detail. */ public SearchBox getSearchBox() { if ((mWebViewCore == null) || (mWebViewCore.getBrowserFrame() == null)) { @@ -5638,6 +5524,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc addAccessibilityApisToJavaScript(); mTouchEventQueue.reset(); + updateHwAccelerated(); } @Override @@ -5657,6 +5544,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } removeAccessibilityApisFromJavaScript(); + updateHwAccelerated(); } @Override @@ -5779,9 +5667,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mVisibleContentRect, getScale()); } - /** - * @hide - */ @Override public boolean setFrame(int left, int top, int right, int bottom) { boolean changed = mWebViewPrivate.super_setFrame(left, top, right, bottom); @@ -6221,6 +6106,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc ted.mReprocess = mDeferTouchProcess; ted.mNativeLayer = mCurrentScrollingLayerId; ted.mNativeLayerRect.set(mScrollingLayerRect); + ted.mMotionEvent = MotionEvent.obtain(ev); ted.mSequence = mTouchEventQueue.nextTouchSequence(); mTouchEventQueue.preQueueTouchEventData(ted); mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); @@ -6375,8 +6261,15 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc case MotionEvent.ACTION_UP: { mGestureDetector.onTouchEvent(ev); if (mTouchInEditText && mConfirmMove) { + stopTouch(); break; // We've been scrolling the edit text. } + if (!mConfirmMove && mIsEditingText && mSelectionStarted && + mIsCaretSelection) { + showPasteWindow(); + stopTouch(); + break; + } // pass the touch events from UI thread to WebCore thread if (shouldForwardTouchEvent()) { TouchEventData ted = new TouchEventData(); @@ -6762,7 +6655,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc syncSelectionCursors(); if (mIsCaretSelection) { resetCaretTimer(); - showPasteWindow(); } invalidate(); } @@ -6854,7 +6746,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private DrawData mLoadedPicture; public void setMapTrackballToArrowKeys(boolean setMap) { - checkThread(); mMapTrackballToArrowKeys = setMap; } @@ -7081,7 +6972,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } public void flingScroll(int vx, int vy) { - checkThread(); mScroller.fling(getScrollX(), getScrollY(), vx, vy, 0, computeMaxScrollX(), 0, computeMaxScrollY(), mOverflingDistance, mOverflingDistance); invalidate(); @@ -7203,7 +7093,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc @Override @Deprecated public View getZoomControls() { - checkThread(); if (!getSettings().supportZoom()) { Log.w(LOGTAG, "This WebView doesn't support zoom."); return null; @@ -7232,7 +7121,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public boolean canZoomIn() { - checkThread(); return mZoomManager.canZoomIn(); } @@ -7241,7 +7129,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public boolean canZoomOut() { - checkThread(); return mZoomManager.canZoomOut(); } @@ -7250,7 +7137,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public boolean zoomIn() { - checkThread(); return mZoomManager.zoomIn(); } @@ -7259,7 +7145,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc */ @Override public boolean zoomOut() { - checkThread(); return mZoomManager.zoomOut(); } @@ -7296,10 +7181,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } } - void sendPluginDrawMsg() { - mWebViewCore.sendMessage(EventHub.PLUGIN_SURFACE_READY); - } - /* * Return true if the rect (e.g. plugin) is fully visible and maximized * inside the WebView. @@ -7553,9 +7434,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE, null, 1000); } - /** - * @hide - */ public synchronized WebViewCore getWebViewCore() { return mWebViewCore; } @@ -8525,6 +8403,14 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } break; + case SHOW_CARET_HANDLE: + if (!mSelectingText && mIsEditingText && mIsCaretSelection) { + setupWebkitSelect(); + resetCaretTimer(); + showPasteWindow(); + } + break; + default: super.handleMessage(msg); break; @@ -8729,9 +8615,19 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc void onPageSwapOccurred(boolean notifyAnimationStarted); } - /** @hide Called by JNI when pages are swapped (only occurs with hardware + long mLastSwapTime; + double mAverageSwapFps; + + /** Called by JNI when pages are swapped (only occurs with hardware * acceleration) */ protected void pageSwapCallback(boolean notifyAnimationStarted) { + if (DebugFlags.MEASURE_PAGE_SWAP_FPS) { + long now = System.currentTimeMillis(); + long diff = now - mLastSwapTime; + mAverageSwapFps = ((1000.0 / diff) + mAverageSwapFps) / 2; + Log.d(LOGTAG, "page swap fps: " + mAverageSwapFps); + mLastSwapTime = now; + } mWebViewCore.resumeWebKitDraw(); if (notifyAnimationStarted) { mWebViewCore.sendMessage(EventHub.NOTIFY_ANIMATION_STARTED); @@ -8793,7 +8689,12 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" + b.left+","+b.top+","+b.right+","+b.bottom+"}"); } - invalidateContentRect(draw.mInvalRegion.getBounds()); + Rect invalBounds = draw.mInvalRegion.getBounds(); + if (!invalBounds.isEmpty()) { + invalidateContentRect(invalBounds); + } else { + mWebView.invalidate(); + } if (mPictureListener != null) { mPictureListener.onNewPicture(getWebView(), capturePicture()); @@ -8824,13 +8725,20 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc (data.mStart != data.mEnd || (mFieldPointer == nodePointer && mFieldPointer != 0))) { mIsCaretSelection = (data.mStart == data.mEnd); - if (!mSelectingText) { - setupWebkitSelect(); - } else if (!mSelectionStarted) { - syncSelectionCursors(); - } - if (mIsCaretSelection) { - resetCaretTimer(); + if (mIsCaretSelection && + (mInputConnection == null || + mInputConnection.getEditable().length() == 0)) { + // There's no text, don't show caret handle. + selectionDone(); + } else { + if (!mSelectingText) { + setupWebkitSelect(); + } else if (!mSelectionStarted) { + syncSelectionCursors(); + } + if (mIsCaretSelection) { + resetCaretTimer(); + } } } else { selectionDone(); @@ -9112,8 +9020,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // after the page regains focus. mListBoxMessage = Message.obtain(null, EventHub.SINGLE_LISTBOX_CHOICE, (int) id, 0); - mListBoxDialog.dismiss(); - mListBoxDialog = null; + if (mListBoxDialog != null) { + mListBoxDialog.dismiss(); + mListBoxDialog = null; + } } }); if (mSelection != -1) { @@ -9288,7 +9198,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * view-specific zoom, scroll offset, or other changes. It does not draw * any view-specific chrome, such as progress or URL bars. * - * @hide only needs to be accessible to Browser and testing + * only needs to be accessible to Browser and testing */ public void drawPage(Canvas canvas) { calcOurContentVisibleRectF(mVisibleContentRect); @@ -9298,7 +9208,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc /** * Enable the communication b/t the webView and VideoViewProxy * - * @hide only used by the Browser + * only used by the Browser */ public void setHTML5VideoViewProxy(HTML5VideoViewProxy proxy) { mHTML5VideoViewProxy = proxy; @@ -9308,7 +9218,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc * Set the time to wait between passing touches to WebCore. See also the * TOUCH_SENT_INTERVAL member for further discussion. * - * @hide This is only used by the DRT test application. + * This is only used by the DRT test application. */ public void setTouchInterval(int interval) { mCurrentTouchInterval = interval; @@ -9335,34 +9245,46 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc return mViewManager; } - private static void checkThread() { - if (Looper.myLooper() != Looper.getMainLooper()) { - Throwable throwable = new Throwable( - "Warning: A WebView method was called on thread '" + - Thread.currentThread().getName() + "'. " + - "All WebView methods must be called on the UI thread. " + - "Future versions of WebView may not support use on other threads."); - Log.w(LOGTAG, Log.getStackTraceString(throwable)); - StrictMode.onWebViewMethodCalledOnWrongThread(throwable); - } - } - - /** @hide send content invalidate */ + /** send content invalidate */ protected void contentInvalidateAll() { if (mWebViewCore != null && !mBlockWebkitViewMessages) { mWebViewCore.sendMessage(EventHub.CONTENT_INVALIDATE_ALL); } } - /** @hide discard all textures from tiles. Used in Profiled WebView */ + /** discard all textures from tiles. Used in Profiled WebView */ public void discardAllTextures() { nativeDiscardAllTextures(); } + @Override + public void setLayerType(int layerType, Paint paint) { + updateHwAccelerated(); + } + + private void updateHwAccelerated() { + if (mNativeClass == 0) { + return; + } + boolean hwAccelerated = false; + if (mWebView.isHardwareAccelerated() + && mWebView.getLayerType() != View.LAYER_TYPE_SOFTWARE) { + hwAccelerated = true; + } + int result = nativeSetHwAccelerated(mNativeClass, hwAccelerated); + if (mWebViewCore == null || mBlockWebkitViewMessages) { + return; + } + if (result == 1) { + // Sync layers + mWebViewCore.layersDraw(); + } + } + /** * Begin collecting per-tile profiling data * - * @hide only used by profiling tests + * only used by profiling tests */ public void tileProfilingStart() { nativeTileProfilingStart(); @@ -9370,29 +9292,29 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc /** * Return per-tile profiling data * - * @hide only used by profiling tests + * only used by profiling tests */ public float tileProfilingStop() { return nativeTileProfilingStop(); } - /** @hide only used by profiling tests */ + /** only used by profiling tests */ public void tileProfilingClear() { nativeTileProfilingClear(); } - /** @hide only used by profiling tests */ + /** only used by profiling tests */ public int tileProfilingNumFrames() { return nativeTileProfilingNumFrames(); } - /** @hide only used by profiling tests */ + /** only used by profiling tests */ public int tileProfilingNumTilesInFrame(int frame) { return nativeTileProfilingNumTilesInFrame(frame); } - /** @hide only used by profiling tests */ + /** only used by profiling tests */ public int tileProfilingGetInt(int frame, int tile, String key) { return nativeTileProfilingGetInt(frame, tile, key); } - /** @hide only used by profiling tests */ + /** only used by profiling tests */ public float tileProfilingGetFloat(int frame, int tile, String key) { return nativeTileProfilingGetFloat(frame, tile, key); } @@ -9480,4 +9402,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private static native boolean nativeIsBaseFirst(int instance); private static native void nativeMapLayerRect(int instance, int layerId, Rect rect); + // Returns 1 if a layer sync is needed, else 0 + private static native int nativeSetHwAccelerated(int instance, boolean hwAccelerated); } diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index d784b08..afb2992 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -71,9 +71,8 @@ public final class WebViewCore { * WebViewCore always executes in the same thread as the native webkit. */ - // The WebView that corresponds to this WebViewCore. - // TODO: rename this field (and its getter) to mWebViewClassic or mWebViewImpl. - private WebViewClassic mWebView; + // The WebViewClassic that corresponds to this WebViewCore. + private WebViewClassic mWebViewClassic; // Proxy for handling callbacks from native code private final CallbackProxy mCallbackProxy; // Settings object for maintaining all settings @@ -149,7 +148,7 @@ public final class WebViewCore { Map<String, Object> javascriptInterfaces) { // No need to assign this in the WebCore thread. mCallbackProxy = proxy; - mWebView = w; + mWebViewClassic = w; mJavascriptInterfaces = javascriptInterfaces; // This context object is used to initialize the WebViewCore during // subwindow creation. @@ -182,7 +181,7 @@ public final class WebViewCore { // ready. mEventHub = new EventHub(); // Create a WebSettings object for maintaining all settings - mSettings = new WebSettingsClassic(mContext, mWebView); + mSettings = new WebSettingsClassic(mContext, mWebViewClassic); // The WebIconDatabase needs to be initialized within the UI thread so // just request the instance here. WebIconDatabase.getInstance(); @@ -235,8 +234,8 @@ public final class WebViewCore { // Send a message back to WebView to tell it that we have set up the // WebCore thread. - if (mWebView != null) { - Message.obtain(mWebView.mPrivateHandler, + if (mWebViewClassic != null) { + Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.WEBCORE_INITIALIZED_MSG_ID, mNativeClass, 0).sendToTarget(); } @@ -331,8 +330,8 @@ public final class WebViewCore { * @param nodePointer The node which just blurred. */ private void formDidBlur(int nodePointer) { - if (mWebView == null) return; - Message.obtain(mWebView.mPrivateHandler, WebViewClassic.FORM_DID_BLUR, + if (mWebViewClassic == null) return; + Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.FORM_DID_BLUR, nodePointer, 0).sendToTarget(); } @@ -340,8 +339,8 @@ public final class WebViewCore { * Called by JNI when the focus node changed. */ private void focusNodeChanged(int nodePointer, WebKitHitTest hitTest) { - if (mWebView == null) return; - mWebView.mPrivateHandler.obtainMessage(WebViewClassic.FOCUS_NODE_CHANGED, + if (mWebViewClassic == null) return; + mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.FOCUS_NODE_CHANGED, nodePointer, 0, hitTest).sendToTarget(); } @@ -349,8 +348,8 @@ public final class WebViewCore { * Called by JNI to advance focus to the next view. */ private void chromeTakeFocus(int webkitDirection) { - if (mWebView == null) return; - Message m = mWebView.mPrivateHandler.obtainMessage( + if (mWebViewClassic == null) return; + Message m = mWebViewClassic.mPrivateHandler.obtainMessage( WebViewClassic.TAKE_FOCUS); m.arg1 = mapDirection(webkitDirection); m.sendToTarget(); @@ -561,8 +560,8 @@ public final class WebViewCore { * Notify the webview that we want to display the video layer fullscreen. */ protected void enterFullscreenForVideoLayer(int layerId, String url) { - if (mWebView == null) return; - Message message = Message.obtain(mWebView.mPrivateHandler, + if (mWebViewClassic == null) return; + Message message = Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.ENTER_FULLSCREEN_VIDEO, layerId, 0); message.obj = url; message.sendToTarget(); @@ -573,8 +572,8 @@ public final class WebViewCore { * This is called through JNI by webcore. */ protected void exitFullscreenVideo() { - if (mWebView == null) return; - Message message = Message.obtain(mWebView.mPrivateHandler, + if (mWebViewClassic == null) return; + Message message = Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.EXIT_FULLSCREEN_VIDEO); message.sendToTarget(); } @@ -1256,7 +1255,7 @@ public final class WebViewCore { return; } - if (mWebView == null || mNativeClass == 0) { + if (mWebViewClassic == null || mNativeClass == 0) { if (DebugFlags.WEB_VIEW_CORE) { Log.w(LOGTAG, "Rejecting message " + msg.what + " because we are destroyed"); @@ -1294,7 +1293,7 @@ public final class WebViewCore { mBrowserFrame = null; mSettings.onDestroyed(); mNativeClass = 0; - mWebView = null; + mWebViewClassic = null; } break; @@ -1537,7 +1536,7 @@ public final class WebViewCore { yArray, count, ted.mActionIndex, ted.mMetaState); Message.obtain( - mWebView.mPrivateHandler, + mWebViewClassic.mPrivateHandler, WebViewClassic.PREVENT_TOUCH_ID, ted.mAction, ted.mNativeResult ? 1 : 0, @@ -1607,7 +1606,7 @@ public final class WebViewCore { String modifiedSelectionString = nativeModifySelection(mNativeClass, msg.arg1, msg.arg2); - mWebView.mPrivateHandler.obtainMessage( + mWebViewClassic.mPrivateHandler.obtainMessage( WebViewClassic.SELECTION_STRING_CHANGED, modifiedSelectionString).sendToTarget(); break; @@ -1653,7 +1652,7 @@ public final class WebViewCore { (WebViewClassic.SaveWebArchiveMessage)msg.obj; saveMessage.mResultFile = saveWebArchive(saveMessage.mBasename, saveMessage.mAutoname); - mWebView.mPrivateHandler.obtainMessage( + mWebViewClassic.mPrivateHandler.obtainMessage( WebViewClassic.SAVE_WEBARCHIVE_FINISHED, saveMessage).sendToTarget(); break; @@ -1666,7 +1665,7 @@ public final class WebViewCore { case SPLIT_PICTURE_SET: nativeSplitContent(mNativeClass, msg.arg1); - mWebView.mPrivateHandler.obtainMessage( + mWebViewClassic.mPrivateHandler.obtainMessage( WebViewClassic.REPLACE_BASE_CONTENT, msg.arg1, 0); mSplitPictureIsScheduled = false; break; @@ -1714,7 +1713,7 @@ public final class WebViewCore { d.mNativeLayer, d.mNativeLayerRect); } WebKitHitTest hit = performHitTest(d.mX, d.mY, d.mSlop, true); - mWebView.mPrivateHandler.obtainMessage( + mWebViewClassic.mPrivateHandler.obtainMessage( WebViewClassic.HIT_TEST_RESULT, hit) .sendToTarget(); break; @@ -1725,8 +1724,8 @@ public final class WebViewCore { case AUTOFILL_FORM: nativeAutoFillForm(mNativeClass, msg.arg1); - mWebView.mPrivateHandler.obtainMessage(WebViewClassic.AUTOFILL_COMPLETE, null) - .sendToTarget(); + mWebViewClassic.mPrivateHandler.obtainMessage( + WebViewClassic.AUTOFILL_COMPLETE, null).sendToTarget(); break; case EXECUTE_JS: @@ -1734,7 +1733,8 @@ public final class WebViewCore { if (DebugFlags.WEB_VIEW_CORE) { Log.d(LOGTAG, "Executing JS : " + msg.obj); } - mBrowserFrame.stringByEvaluatingJavaScriptFromString((String) msg.obj); + mBrowserFrame.stringByEvaluatingJavaScriptFromString( + (String) msg.obj); } break; case SCROLL_LAYER: @@ -1756,7 +1756,8 @@ public final class WebViewCore { handles[0], handles[1], handles[2], handles[3]); if (copiedText != null) { - mWebView.mPrivateHandler.obtainMessage(WebViewClassic.COPY_TO_CLIPBOARD, copiedText) + mWebViewClassic.mPrivateHandler.obtainMessage( + WebViewClassic.COPY_TO_CLIPBOARD, copiedText) .sendToTarget(); } break; @@ -1777,7 +1778,10 @@ public final class WebViewCore { case SELECT_WORD_AT: { int x = msg.arg1; int y = msg.arg2; - nativeSelectWordAt(mNativeClass, x, y); + if (!nativeSelectWordAt(mNativeClass, x, y)) { + mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.SHOW_CARET_HANDLE) + .sendToTarget(); + } break; } case SELECT_ALL: @@ -2028,7 +2032,7 @@ public final class WebViewCore { if (keyCode >= KeyEvent.KEYCODE_DPAD_UP && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { if (canTakeFocusDirection != 0 && isDown) { - Message m = mWebView.mPrivateHandler.obtainMessage( + Message m = mWebViewClassic.mPrivateHandler.obtainMessage( WebViewClassic.TAKE_FOCUS); m.arg1 = canTakeFocusDirection; m.sendToTarget(); @@ -2101,7 +2105,8 @@ public final class WebViewCore { width = mViewportWidth; } else { // For mobile web site. - width = Math.round(mWebView.getViewWidth() / mWebView.getDefaultZoomScale()); + width = Math.round(mWebViewClassic.getViewWidth() / + mWebViewClassic.getDefaultZoomScale()); } } return width; @@ -2193,8 +2198,8 @@ public final class WebViewCore { // If anything more complex than position has been touched, let's do a full draw webkitDraw(); } - mWebView.mPrivateHandler.removeMessages(WebViewClassic.INVAL_RECT_MSG_ID); - mWebView.mPrivateHandler.sendMessageAtFrontOfQueue(mWebView.mPrivateHandler + mWebViewClassic.mPrivateHandler.removeMessages(WebViewClassic.INVAL_RECT_MSG_ID); + mWebViewClassic.mPrivateHandler.sendMessageAtFrontOfQueue(mWebViewClassic.mPrivateHandler .obtainMessage(WebViewClassic.INVAL_RECT_MSG_ID)); } @@ -2234,7 +2239,7 @@ public final class WebViewCore { draw.mBaseLayer = nativeRecordContent(mNativeClass, draw.mInvalRegion, draw.mContentSize); if (draw.mBaseLayer == 0) { - if (mWebView != null && !mWebView.isPaused()) { + if (mWebViewClassic != null && !mWebViewClassic.isPaused()) { if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort, resending draw message"); mEventHub.sendMessageDelayed(Message.obtain(null, EventHub.WEBKIT_DRAW), 10); } else { @@ -2247,7 +2252,7 @@ public final class WebViewCore { } private void webkitDraw(DrawData draw) { - if (mWebView != null) { + if (mWebViewClassic != null) { draw.mFocusSizeChanged = nativeFocusBoundsChanged(mNativeClass); draw.mViewSize = new Point(mCurrentViewWidth, mCurrentViewHeight); if (mSettings.getUseWideViewPort()) { @@ -2266,7 +2271,8 @@ public final class WebViewCore { mFirstLayoutForNonStandardLoad = false; } if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw NEW_PICTURE_MSG_ID"); - Message.obtain(mWebView.mPrivateHandler, + pauseWebKitDraw(); + Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.NEW_PICTURE_MSG_ID, draw).sendToTarget(); } } @@ -2356,7 +2362,7 @@ public final class WebViewCore { // called from JNI or WebView thread /* package */ void contentDraw() { synchronized (this) { - if (mWebView == null || mBrowserFrame == null) { + if (mWebViewClassic == null || mBrowserFrame == null) { // We were destroyed return; } @@ -2394,8 +2400,8 @@ public final class WebViewCore { mRestoredY = y; return; } - if (mWebView != null) { - Message msg = Message.obtain(mWebView.mPrivateHandler, + if (mWebViewClassic != null) { + Message msg = Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.SCROLL_TO_MSG_ID, animate ? 1 : 0, onlyIfImeIsShowing ? 1 : 0, new Point(x, y)); if (mDrawIsScheduled) { @@ -2417,8 +2423,8 @@ public final class WebViewCore { in WebView since it (and its thread) know the current scale factor. */ private void sendViewInvalidate(int left, int top, int right, int bottom) { - if (mWebView != null) { - Message.obtain(mWebView.mPrivateHandler, + if (mWebViewClassic != null) { + Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.INVAL_RECT_MSG_ID, new Rect(left, top, right, bottom)).sendToTarget(); } @@ -2433,10 +2439,20 @@ public final class WebViewCore { mRepaintScheduled = false; } - // Gets the WebView corresponding to this WebViewCore. Note that the - // WebView object must only be used on the UI thread. - /* package */ WebViewClassic getWebView() { - return mWebView; + // Gets the WebViewClassic corresponding to this WebViewCore. Note that the + // WebViewClassic object must only be used on the UI thread. + /* package */ WebViewClassic getWebViewClassic() { + return mWebViewClassic; + } + + // Called by JNI + private WebView getWebView() { + return mWebViewClassic.getWebView(); + } + + // Called by JNI + private void sendPluginDrawMsg() { + sendMessage(EventHub.PLUGIN_SURFACE_READY); } private native void setViewportSettingsFromNative(int nativeClass); @@ -2449,7 +2465,7 @@ public final class WebViewCore { mBrowserFrame.didFirstLayout(); - if (mWebView == null) return; + if (mWebViewClassic == null) return; boolean updateViewState = standardLoad || mIsRestored; setupViewport(updateViewState); @@ -2457,11 +2473,11 @@ public final class WebViewCore { // be called after the WebView updates its state. If updateRestoreState // is false, start to draw now as it is ready. if (!updateViewState) { - mWebView.mViewManager.postReadyToDrawAll(); + mWebViewClassic.mViewManager.postReadyToDrawAll(); } // remove the touch highlight when moving to a new page - mWebView.mPrivateHandler.sendEmptyMessage( + mWebViewClassic.mPrivateHandler.sendEmptyMessage( WebViewClassic.HIT_TEST_RESULT); // reset the scroll position, the restored offset and scales @@ -2477,7 +2493,7 @@ public final class WebViewCore { } private void setupViewport(boolean updateViewState) { - if (mWebView == null || mSettings == null) { + if (mWebViewClassic == null || mSettings == null) { // We've been destroyed or are being destroyed, return early return; } @@ -2525,8 +2541,8 @@ public final class WebViewCore { adjust = (float) mContext.getResources().getDisplayMetrics().densityDpi / mViewportDensityDpi; } - if (adjust != mWebView.getDefaultZoomScale()) { - Message.obtain(mWebView.mPrivateHandler, + if (adjust != mWebViewClassic.getDefaultZoomScale()) { + Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.UPDATE_ZOOM_DENSITY, adjust).sendToTarget(); } int defaultScale = (int) (adjust * 100); @@ -2577,7 +2593,7 @@ public final class WebViewCore { // for non-mobile site, we don't need minPrefWidth, set it as 0 viewState.mScrollX = 0; viewState.mShouldStartScrolledRight = false; - Message.obtain(mWebView.mPrivateHandler, + Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.UPDATE_ZOOM_RANGE, viewState).sendToTarget(); return; } @@ -2591,7 +2607,7 @@ public final class WebViewCore { // this may happen when WebView just starts. This is not perfect as // we call WebView method from WebCore thread. But not perfect // reference is better than no reference. - webViewWidth = mWebView.getViewWidth(); + webViewWidth = mWebViewClassic.getViewWidth(); viewportWidth = (int) (webViewWidth / adjust); if (viewportWidth == 0) { if (DebugFlags.WEB_VIEW_CORE) { @@ -2640,17 +2656,17 @@ public final class WebViewCore { } } - if (mWebView.mHeightCanMeasure) { + if (mWebViewClassic.mHeightCanMeasure) { // Trick to ensure that the Picture has the exact height for the // content by forcing to layout with 0 height after the page is // ready, which is indicated by didFirstLayout. This is essential to // get rid of the white space in the GMail which uses WebView for // message view. - mWebView.mLastHeightSent = 0; + mWebViewClassic.mLastHeightSent = 0; // Send a negative scale to indicate that WebCore should reuse // the current scale WebViewClassic.ViewSizeData data = new WebViewClassic.ViewSizeData(); - data.mWidth = mWebView.mLastWidthSent; + data.mWidth = mWebViewClassic.mLastWidthSent; data.mHeight = 0; // if mHeightCanMeasure is true, getUseWideViewPort() can't be // true. It is safe to use mWidth for mTextWrapWidth. @@ -2671,7 +2687,7 @@ public final class WebViewCore { if (viewportWidth == 0) { // Trick to ensure VIEW_SIZE_CHANGED will be sent from WebView // to WebViewCore - mWebView.mLastWidthSent = 0; + mWebViewClassic.mLastWidthSent = 0; } else { WebViewClassic.ViewSizeData data = new WebViewClassic.ViewSizeData(); // mViewScale as 0 means it is in zoom overview mode. So we don't @@ -2699,7 +2715,7 @@ public final class WebViewCore { if (mSettings.isNarrowColumnLayout()) { // In case of automatic text reflow in fixed view port mode. mInitialViewState.mTextWrapScale = - mWebView.computeReadingLevelScale(data.mScale); + mWebViewClassic.computeReadingLevelScale(data.mScale); } } else { // Scale is given such as when page is restored, use it. @@ -2720,7 +2736,7 @@ public final class WebViewCore { // are calling a WebView method from the WebCore thread. But this is preferable // to syncing an incorrect height. data.mHeight = mCurrentViewHeight == 0 ? - Math.round(mWebView.getViewHeight() / data.mScale) + Math.round(mWebViewClassic.getViewHeight() / data.mScale) : Math.round((float) mCurrentViewHeight * data.mWidth / viewportWidth); data.mTextWrapWidth = Math.round(webViewWidth / mInitialViewState.mTextWrapScale); @@ -2749,8 +2765,8 @@ public final class WebViewCore { // called by JNI private void needTouchEvents(boolean need) { - if (mWebView != null) { - Message.obtain(mWebView.mPrivateHandler, + if (mWebViewClassic != null) { + Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.WEBCORE_NEED_TOUCH_EVENTS, need ? 1 : 0, 0) .sendToTarget(); } @@ -2759,8 +2775,8 @@ public final class WebViewCore { // called by JNI private void updateTextfield(int ptr, boolean changeToPassword, String text, int textGeneration) { - if (mWebView != null) { - Message msg = Message.obtain(mWebView.mPrivateHandler, + if (mWebViewClassic != null) { + Message msg = Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.UPDATE_TEXTFIELD_TEXT_MSG_ID, ptr, textGeneration, text); msg.getData().putBoolean("password", changeToPassword); @@ -2771,8 +2787,8 @@ public final class WebViewCore { // called by JNI private void updateTextSelection(int pointer, int start, int end, int textGeneration, int selectionPtr) { - if (mWebView != null) { - Message.obtain(mWebView.mPrivateHandler, + if (mWebViewClassic != null) { + Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.UPDATE_TEXT_SELECTION_MSG_ID, pointer, textGeneration, new TextSelectionData(start, end, selectionPtr)).sendToTarget(); } @@ -2781,10 +2797,10 @@ public final class WebViewCore { // called by JNI private void updateTextSizeAndScroll(int pointer, int width, int height, int scrollX, int scrollY) { - if (mWebView != null) { + if (mWebViewClassic != null) { Rect rect = new Rect(-scrollX, -scrollY, width - scrollX, height - scrollY); - Message.obtain(mWebView.mPrivateHandler, + Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.EDIT_TEXT_SIZE_CHANGED, pointer, 0, rect) .sendToTarget(); } @@ -2792,20 +2808,20 @@ public final class WebViewCore { // called by JNI private void clearTextEntry() { - if (mWebView == null) return; - Message.obtain(mWebView.mPrivateHandler, + if (mWebViewClassic == null) return; + Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.CLEAR_TEXT_ENTRY).sendToTarget(); } // called by JNI private void initEditField(int start, int end, int selectionPtr, TextFieldInitData initData) { - if (mWebView == null) { + if (mWebViewClassic == null) { return; } - Message.obtain(mWebView.mPrivateHandler, + Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.INIT_EDIT_FIELD, initData).sendToTarget(); - Message.obtain(mWebView.mPrivateHandler, + Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID, initData.mFieldPointer, 0, new TextSelectionData(start, end, selectionPtr)) @@ -2815,10 +2831,10 @@ public final class WebViewCore { // called by JNI private void updateMatchCount(int matchIndex, int matchCount, String findText) { - if (mWebView == null) { + if (mWebViewClassic == null) { return; } - Message.obtain(mWebView.mPrivateHandler, + Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.UPDATE_MATCH_COUNT, matchIndex, matchCount, findText).sendToTarget(); } @@ -2842,32 +2858,32 @@ public final class WebViewCore { // called by JNI private void requestListBox(String[] array, int[] enabledArray, int[] selectedArray) { - if (mWebView != null) { - mWebView.requestListBox(array, enabledArray, selectedArray); + if (mWebViewClassic != null) { + mWebViewClassic.requestListBox(array, enabledArray, selectedArray); } } // called by JNI private void requestListBox(String[] array, int[] enabledArray, int selection) { - if (mWebView != null) { - mWebView.requestListBox(array, enabledArray, selection); + if (mWebViewClassic != null) { + mWebViewClassic.requestListBox(array, enabledArray, selection); } } // called by JNI private void requestKeyboard(boolean showKeyboard) { - if (mWebView != null) { - Message.obtain(mWebView.mPrivateHandler, + if (mWebViewClassic != null) { + Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.REQUEST_KEYBOARD, showKeyboard ? 1 : 0, 0) .sendToTarget(); } } private void setWebTextViewAutoFillable(int queryId, String preview) { - if (mWebView != null) { - Message.obtain(mWebView.mPrivateHandler, WebViewClassic.SET_AUTOFILLABLE, + if (mWebViewClassic != null) { + Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.SET_AUTOFILLABLE, new AutoFillData(queryId, preview)) .sendToTarget(); } @@ -2879,8 +2895,9 @@ public final class WebViewCore { // called by JNI private void keepScreenOn(boolean screenOn) { - if (mWebView != null) { - Message message = mWebView.mPrivateHandler.obtainMessage(WebViewClassic.SCREEN_ON); + if (mWebViewClassic != null) { + Message message = mWebViewClassic.mPrivateHandler.obtainMessage( + WebViewClassic.SCREEN_ON); message.arg1 = screenOn ? 1 : 0; message.sendToTarget(); } @@ -2889,7 +2906,7 @@ public final class WebViewCore { // called by JNI private Class<?> getPluginClass(String libName, String clsName) { - if (mWebView == null) { + if (mWebViewClassic == null) { return null; } @@ -2916,11 +2933,12 @@ public final class WebViewCore { // called by JNI. PluginWidget function to launch a full-screen view using a // View object provided by the plugin class. private void showFullScreenPlugin(ViewManager.ChildView childView, int orientation, int npp) { - if (mWebView == null) { + if (mWebViewClassic == null) { return; } - Message message = mWebView.mPrivateHandler.obtainMessage(WebViewClassic.SHOW_FULLSCREEN); + Message message = mWebViewClassic.mPrivateHandler.obtainMessage( + WebViewClassic.SHOW_FULLSCREEN); message.obj = childView.mView; message.arg1 = orientation; message.arg2 = npp; @@ -2929,15 +2947,15 @@ public final class WebViewCore { // called by JNI private void hideFullScreenPlugin() { - if (mWebView == null) { + if (mWebViewClassic == null) { return; } - mWebView.mPrivateHandler.obtainMessage(WebViewClassic.HIDE_FULLSCREEN) + mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.HIDE_FULLSCREEN) .sendToTarget(); } private ViewManager.ChildView createSurface(View pluginView) { - if (mWebView == null) { + if (mWebViewClassic == null) { return null; } @@ -2952,7 +2970,7 @@ public final class WebViewCore { if(pluginView instanceof SurfaceView) ((SurfaceView)pluginView).setZOrderOnTop(true); - ViewManager.ChildView view = mWebView.mViewManager.createView(); + ViewManager.ChildView view = mWebViewClassic.mViewManager.createView(); view.mView = pluginView; return view; } @@ -2992,7 +3010,7 @@ public final class WebViewCore { private void showRect(int left, int top, int width, int height, int contentWidth, int contentHeight, float xPercentInDoc, float xPercentInView, float yPercentInDoc, float yPercentInView) { - if (mWebView != null) { + if (mWebViewClassic != null) { ShowRectData data = new ShowRectData(); data.mLeft = left; data.mTop = top; @@ -3004,26 +3022,26 @@ public final class WebViewCore { data.mXPercentInView = xPercentInView; data.mYPercentInDoc = yPercentInDoc; data.mYPercentInView = yPercentInView; - Message.obtain(mWebView.mPrivateHandler, WebViewClassic.SHOW_RECT_MSG_ID, + Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.SHOW_RECT_MSG_ID, data).sendToTarget(); } } // called by JNI private void centerFitRect(int x, int y, int width, int height) { - if (mWebView == null) { + if (mWebViewClassic == null) { return; } - mWebView.mPrivateHandler.obtainMessage(WebViewClassic.CENTER_FIT_RECT, + mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.CENTER_FIT_RECT, new Rect(x, y, x + width, y + height)).sendToTarget(); } // called by JNI private void setScrollbarModes(int hMode, int vMode) { - if (mWebView == null) { + if (mWebViewClassic == null) { return; } - mWebView.mPrivateHandler.obtainMessage(WebViewClassic.SET_SCROLLBAR_MODES, + mWebViewClassic.mPrivateHandler.obtainMessage(WebViewClassic.SET_SCROLLBAR_MODES, hMode, vMode).sendToTarget(); } @@ -3106,7 +3124,7 @@ public final class WebViewCore { private native void nativeSelectText(int nativeClass, int startX, int startY, int endX, int endY); private native void nativeClearTextSelection(int nativeClass); - private native void nativeSelectWordAt(int nativeClass, int x, int y); + private native boolean nativeSelectWordAt(int nativeClass, int x, int y); private native void nativeSelectAll(int nativeClass); private static native void nativeCertTrustChanged(); diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java index 757a619..6c35f19 100644 --- a/core/java/android/webkit/WebViewDatabase.java +++ b/core/java/android/webkit/WebViewDatabase.java @@ -144,7 +144,6 @@ public class WebViewDatabase { null); } } - mDatabase.enableWriteAheadLogging(); // mDatabase should not be null, // the only case is RequestAPI test has problem to create db @@ -163,11 +162,6 @@ public class WebViewDatabase { mDatabase.endTransaction(); } } - - // use per table Mutex lock, turn off database lock, this - // improves performance as database's ReentrantLock is - // expansive - mDatabase.setLockingEnabled(false); } private static void upgradeDatabase() { diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java index 9016fbc..f049198 100644 --- a/core/java/android/webkit/WebViewProvider.java +++ b/core/java/android/webkit/WebViewProvider.java @@ -19,6 +19,7 @@ package android.webkit; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.Picture; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -191,7 +192,7 @@ public interface WebViewProvider { public WebBackForwardList copyBackForwardList(); - public void setFindListener(FindListener listener); + public void setFindListener(WebView.FindListener listener); public void findNext(boolean forward); @@ -337,6 +338,8 @@ public interface WebViewProvider { public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate); public void setBackgroundColor(int color); + + public void setLayerType(int layerType, Paint paint); } interface ScrollDelegate { diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index e36afa3..ca5648a 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -133,6 +133,16 @@ public abstract class AbsSeekBar extends ProgressBar { } /** + * Return the drawable used to represent the scroll thumb - the component that + * the user can drag back and forth indicating the current value by its position. + * + * @return The current thumb drawable + */ + public Drawable getThumb() { + return mThumb; + } + + /** * @see #setThumbOffset(int) */ public int getThumbOffset() { diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java index 5096227..aea029b 100644 --- a/core/java/android/widget/AdapterViewFlipper.java +++ b/core/java/android/widget/AdapterViewFlipper.java @@ -127,13 +127,29 @@ public class AdapterViewFlipper extends AdapterViewAnimator { } /** - * How long to wait before flipping to the next view + * Returns the flip interval, in milliseconds. * - * @param milliseconds - * time in milliseconds + * @return the flip interval in milliseconds + * + * @see #setFlipInterval(int) + * + * @attr ref android.R.styleable#AdapterViewFlipper_flipInterval + */ + public int getFlipInterval() { + return mFlipInterval; + } + + /** + * How long to wait before flipping to the next view. + * + * @param flipInterval flip interval in milliseconds + * + * @see #getFlipInterval() + * + * @attr ref android.R.styleable#AdapterViewFlipper_flipInterval */ - public void setFlipInterval(int milliseconds) { - mFlipInterval = milliseconds; + public void setFlipInterval(int flipInterval) { + mFlipInterval = flipInterval; } /** diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index fd93980..c5066b6 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -29,8 +29,8 @@ import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; +import android.view.View; import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; @@ -280,9 +280,7 @@ public class DatePicker extends FrameLayout { reorderSpinners(); // set content descriptions - if (AccessibilityManager.getInstance(mContext).isEnabled()) { - setContentDescriptions(); - } + setContentDescriptions(); } /** @@ -717,20 +715,27 @@ public class DatePicker extends FrameLayout { private void setContentDescriptions() { // Day - String text = mContext.getString(R.string.date_picker_increment_day_button); - mDaySpinner.findViewById(R.id.increment).setContentDescription(text); - text = mContext.getString(R.string.date_picker_decrement_day_button); - mDaySpinner.findViewById(R.id.decrement).setContentDescription(text); + trySetContentDescription(mDaySpinner, R.id.increment, + R.string.date_picker_increment_day_button); + trySetContentDescription(mDaySpinner, R.id.decrement, + R.string.date_picker_decrement_day_button); // Month - text = mContext.getString(R.string.date_picker_increment_month_button); - mMonthSpinner.findViewById(R.id.increment).setContentDescription(text); - text = mContext.getString(R.string.date_picker_decrement_month_button); - mMonthSpinner.findViewById(R.id.decrement).setContentDescription(text); + trySetContentDescription(mMonthSpinner, R.id.increment, + R.string.date_picker_increment_month_button); + trySetContentDescription(mMonthSpinner, R.id.decrement, + R.string.date_picker_decrement_month_button); // Year - text = mContext.getString(R.string.date_picker_increment_year_button); - mYearSpinner.findViewById(R.id.increment).setContentDescription(text); - text = mContext.getString(R.string.date_picker_decrement_year_button); - mYearSpinner.findViewById(R.id.decrement).setContentDescription(text); + trySetContentDescription(mYearSpinner, R.id.increment, + R.string.date_picker_increment_year_button); + trySetContentDescription(mYearSpinner, R.id.decrement, + R.string.date_picker_decrement_year_button); + } + + private void trySetContentDescription(View root, int viewId, int contDescResId) { + View target = root.findViewById(viewId); + if (target != null) { + target.setContentDescription(mContext.getString(contDescResId)); + } } private void updateInputState() { diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index 739bcce..0f1dab5 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -1908,7 +1908,8 @@ public class GridView extends AbsListView { } /** - * Describes how the child views are horizontally aligned. Defaults to Gravity.LEFT + * Set the gravity for this grid. Gravity describes how the child views + * are horizontally aligned. Defaults to Gravity.LEFT * * @param gravity the gravity to apply to this grid's children * @@ -1922,6 +1923,17 @@ public class GridView extends AbsListView { } /** + * Describes how the child views are horizontally aligned. Defaults to Gravity.LEFT + * + * @return the gravity that will be applied to this grid's children + * + * @attr ref android.R.styleable#GridView_gravity + */ + public int getGravity() { + return mGravity; + } + + /** * Set the amount of horizontal (x) spacing to place between each item * in the grid. * @@ -1937,6 +1949,44 @@ public class GridView extends AbsListView { } } + /** + * Returns the amount of horizontal spacing currently used between each item in the grid. + * + * <p>This is only accurate for the current layout. If {@link #setHorizontalSpacing(int)} + * has been called but layout is not yet complete, this method may return a stale value. + * To get the horizontal spacing that was explicitly requested use + * {@link #getRequestedHorizontalSpacing()}.</p> + * + * @return Current horizontal spacing between each item in pixels + * + * @see #setHorizontalSpacing(int) + * @see #getRequestedHorizontalSpacing() + * + * @attr ref android.R.styleable#GridView_horizontalSpacing + */ + public int getHorizontalSpacing() { + return mHorizontalSpacing; + } + + /** + * Returns the requested amount of horizontal spacing between each item in the grid. + * + * <p>The value returned may have been supplied during inflation as part of a style, + * the default GridView style, or by a call to {@link #setHorizontalSpacing(int)}. + * If layout is not yet complete or if GridView calculated a different horizontal spacing + * from what was requested, this may return a different value from + * {@link #getHorizontalSpacing()}.</p> + * + * @return The currently requested horizontal spacing between items, in pixels + * + * @see #setHorizontalSpacing(int) + * @see #getHorizontalSpacing() + * + * @attr ref android.R.styleable#GridView_horizontalSpacing + */ + public int getRequestedHorizontalSpacing() { + return mRequestedHorizontalSpacing; + } /** * Set the amount of vertical (y) spacing to place between each item @@ -1945,6 +1995,8 @@ public class GridView extends AbsListView { * @param verticalSpacing The amount of vertical space between items, * in pixels. * + * @see #getVerticalSpacing() + * * @attr ref android.R.styleable#GridView_verticalSpacing */ public void setVerticalSpacing(int verticalSpacing) { @@ -1955,6 +2007,19 @@ public class GridView extends AbsListView { } /** + * Returns the amount of vertical spacing between each item in the grid. + * + * @return The vertical spacing between items in pixels + * + * @see #setVerticalSpacing(int) + * + * @attr ref android.R.styleable#GridView_verticalSpacing + */ + public int getVerticalSpacing() { + return mVerticalSpacing; + } + + /** * Control how items are stretched to fill their space. * * @param stretchMode Either {@link #NO_STRETCH}, @@ -1988,6 +2053,39 @@ public class GridView extends AbsListView { } /** + * Return the width of a column in the grid. + * + * <p>This may not be valid yet if a layout is pending.</p> + * + * @return The column width in pixels + * + * @see #setColumnWidth(int) + * @see #getRequestedColumnWidth() + * + * @attr ref android.R.styleable#GridView_columnWidth + */ + public int getColumnWidth() { + return mColumnWidth; + } + + /** + * Return the requested width of a column in the grid. + * + * <p>This may not be the actual column width used. Use {@link #getColumnWidth()} + * to retrieve the current real width of a column.</p> + * + * @return The requested column width in pixels + * + * @see #setColumnWidth(int) + * @see #getColumnWidth() + * + * @attr ref android.R.styleable#GridView_columnWidth + */ + public int getRequestedColumnWidth() { + return mRequestedColumnWidth; + } + + /** * Set the number of columns in the grid * * @param numColumns The desired number of columns. diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 0db6ef2..4e13ea1 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -715,6 +715,7 @@ public class HorizontalScrollView extends FrameLayout { } else { super.scrollTo(scrollX, scrollY); } + awakenScrollBars(); } @@ -1204,10 +1205,9 @@ public class HorizontalScrollView extends FrameLayout { } } - awakenScrollBars(); - - // Keep on drawing until the animation has finished. - postInvalidate(); + if (!awakenScrollBars()) { + invalidate(); + } } } @@ -1414,7 +1414,7 @@ public class HorizontalScrollView extends FrameLayout { /** * Return true if child is a descendant of parent, (or equal to the parent). */ - private boolean isViewDescendantOf(View child, View parent) { + private static boolean isViewDescendantOf(View child, View parent) { if (child == parent) { return true; } @@ -1524,7 +1524,7 @@ public class HorizontalScrollView extends FrameLayout { } } - private int clamp(int n, int my, int child) { + private static int clamp(int n, int my, int child) { if (my >= child || n < 0) { return 0; } diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index 3001ea1..b1a75e1 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -223,11 +223,28 @@ public class ImageView extends View { } /** + * True when ImageView is adjusting its bounds + * to preserve the aspect ratio of its drawable + * + * @return whether to adjust the bounds of this view + * to presrve the original aspect ratio of the drawable + * + * @see #setAdjustViewBounds(boolean) + * + * @attr ref android.R.styleable#ImageView_adjustViewBounds + */ + public boolean getAdjustViewBounds() { + return mAdjustViewBounds; + } + + /** * Set this to true if you want the ImageView to adjust its bounds * to preserve the aspect ratio of its drawable. * @param adjustViewBounds Whether to adjust the bounds of this view * to presrve the original aspect ratio of the drawable * + * @see #getAdjustViewBounds() + * * @attr ref android.R.styleable#ImageView_adjustViewBounds */ @android.view.RemotableViewMethod @@ -237,7 +254,20 @@ public class ImageView extends View { setScaleType(ScaleType.FIT_CENTER); } } - + + /** + * The maximum width of this view. + * + * @return The maximum width of this view + * + * @see #setMaxWidth(int) + * + * @attr ref android.R.styleable#ImageView_maxWidth + */ + public int getMaxWidth() { + return mMaxWidth; + } + /** * An optional argument to supply a maximum width for this view. Only valid if * {@link #setAdjustViewBounds(boolean)} has been set to true. To set an image to be a maximum @@ -253,14 +283,29 @@ public class ImageView extends View { * </p> * * @param maxWidth maximum width for this view - * + * + * @see #getMaxWidth() + * * @attr ref android.R.styleable#ImageView_maxWidth */ @android.view.RemotableViewMethod public void setMaxWidth(int maxWidth) { mMaxWidth = maxWidth; } - + + /** + * The maximum height of this view. + * + * @return The maximum height of this view + * + * @see #setMaxHeight(int) + * + * @attr ref android.R.styleable#ImageView_maxHeight + */ + public int getMaxHeight() { + return mMaxHeight; + } + /** * An optional argument to supply a maximum height for this view. Only valid if * {@link #setAdjustViewBounds(boolean)} has been set to true. To set an image to be a @@ -276,7 +321,9 @@ public class ImageView extends View { * </p> * * @param maxHeight maximum height for this view - * + * + * @see #getMaxHeight() + * * @attr ref android.R.styleable#ImageView_maxHeight */ @android.view.RemotableViewMethod @@ -522,7 +569,37 @@ public class ImageView extends View { invalidate(); } } - + + /** + * Return whether this ImageView crops to padding. + * + * @return whether this ImageView crops to padding + * + * @see #setCropToPadding(boolean) + * + * @attr ref android.R.styleable#ImageView_cropToPadding + */ + public boolean getCropToPadding() { + return mCropToPadding; + } + + /** + * Sets whether this ImageView will crop to padding. + * + * @param cropToPadding whether this ImageView will crop to padding + * + * @see #getCropToPadding() + * + * @attr ref android.R.styleable#ImageView_cropToPadding + */ + public void setCropToPadding(boolean cropToPadding) { + if (mCropToPadding != cropToPadding) { + mCropToPadding = cropToPadding; + requestLayout(); + invalidate(); + } + } + private void resolveUri() { if (mDrawable != null) { return; @@ -997,11 +1074,24 @@ public class ImageView extends View { public final void clearColorFilter() { setColorFilter(null); } - + + /** + * Returns the active color filter for this ImageView. + * + * @return the active color filter for this ImageView + * + * @see #setColorFilter(android.graphics.ColorFilter) + */ + public ColorFilter getColorFilter() { + return mColorFilter; + } + /** * Apply an arbitrary colorfilter to the image. * * @param cf the colorfilter to apply (may be null) + * + * @see #getColorFilter() */ public void setColorFilter(ColorFilter cf) { if (mColorFilter != cf) { @@ -1012,6 +1102,37 @@ public class ImageView extends View { } } + /** + * Returns the alpha that will be applied to the drawable of this ImageView. + * + * @return the alpha that will be applied to the drawable of this ImageView + * + * @see #setImageAlpha(int) + */ + public int getImageAlpha() { + return mAlpha; + } + + /** + * Sets the alpha value that should be applied to the image. + * + * @param alpha the alpha value that should be applied to the image + * + * @see #getImageAlpha() + */ + @RemotableViewMethod + public void setImageAlpha(int alpha) { + setAlpha(alpha); + } + + /** + * Sets the alpha value that should be applied to the image. + * + * @param alpha the alpha value that should be applied to the image + * + * @deprecated use #setImageAlpha(int) instead + */ + @Deprecated @RemotableViewMethod public void setAlpha(int alpha) { alpha &= 0xFF; // keep it legal diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index 3335da0..4e56cd6 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -16,10 +16,6 @@ package android.widget; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.annotation.Widget; import android.content.Context; import android.content.res.ColorStateList; @@ -48,22 +44,41 @@ import android.view.ViewConfiguration; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeProvider; import android.view.animation.DecelerateInterpolator; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import com.android.internal.R; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + /** * A widget that enables the user to select a number form a predefined range. - * The widget presents an input field and up and down buttons for selecting the - * current value. Pressing/long-pressing the up and down buttons increments and - * decrements the current value respectively. Touching the input field shows a - * scroll wheel, which when touched allows direct edit - * of the current value. Sliding gestures up or down hide the buttons and the - * input filed, show and rotate the scroll wheel. Flinging is - * also supported. The widget enables mapping from positions to strings such - * that, instead of the position index, the corresponding string is displayed. + * There are two flavors of this widget and which one is presented to the user + * depends on the current theme. + * <ul> + * <li> + * If the current theme is derived from {@link android.R.style#Theme} the widget + * presents the current value as an editable input field with an increment button + * above and a decrement button below. Long pressing the buttons allows for a quick + * change of the current value. Tapping on the input field allows to type in + * a desired value. + * </li> + * <li> + * If the current theme is derived from {@link android.R.style#Theme_Holo} or + * {@link android.R.style#Theme_Holo_Light} the widget presents the current + * value as an editable input field with a lesser value above and a greater + * value below. Tapping on the lesser or greater value selects it by animating + * the number axis up or down to make the chosen value current. Flinging up + * or down allows for multiple increments or decrements of the current value. + * Long pressing on the lesser and greater values also allows for a quick change + * of the current value. Tapping on the current value allows to type in a + * desired value. + * </li> + * </ul> * <p> * For an example of using this widget, see {@link android.widget.TimePicker}. * </p> @@ -74,7 +89,7 @@ public class NumberPicker extends LinearLayout { /** * The number of items show in the selector wheel. */ - public static final int SELECTOR_WHEEL_ITEM_COUNT = 5; + private static final int SELECTOR_WHEEL_ITEM_COUNT = 3; /** * The default update interval during long press. @@ -84,7 +99,7 @@ public class NumberPicker extends LinearLayout { /** * The index of the middle selector item. */ - private static final int SELECTOR_MIDDLE_ITEM_INDEX = 2; + private static final int SELECTOR_MIDDLE_ITEM_INDEX = SELECTOR_WHEEL_ITEM_COUNT / 2; /** * The coefficient by which to adjust (divide) the max fling velocity. @@ -97,19 +112,12 @@ public class NumberPicker extends LinearLayout { private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800; /** - * The duration of scrolling to the next/previous value while changing - * the current value by one, i.e. increment or decrement. + * The duration of scrolling to the next/previous value while changing the + * current value by one, i.e. increment or decrement. */ private static final int CHANGE_CURRENT_BY_ONE_SCROLL_DURATION = 300; /** - * The the delay for showing the input controls after a single tap on the - * input text. - */ - private static final int SHOW_INPUT_CONTROLS_DELAY_MILLIS = ViewConfiguration - .getDoubleTapTimeout(); - - /** * The strength of fading in the top and bottom while drawing the selector. */ private static final float TOP_AND_BOTTOM_FADING_EDGE_STRENGTH = 0.9f; @@ -120,56 +128,31 @@ public class NumberPicker extends LinearLayout { private static final int UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT = 2; /** - * In this state the selector wheel is not shown. - */ - private static final int SELECTOR_WHEEL_STATE_NONE = 0; - - /** - * In this state the selector wheel is small. - */ - private static final int SELECTOR_WHEEL_STATE_SMALL = 1; - - /** - * In this state the selector wheel is large. - */ - private static final int SELECTOR_WHEEL_STATE_LARGE = 2; - - /** - * The alpha of the selector wheel when it is bright. - */ - private static final int SELECTOR_WHEEL_BRIGHT_ALPHA = 255; - - /** - * The alpha of the selector wheel when it is dimmed. + * The default unscaled distance between the selection dividers. */ - private static final int SELECTOR_WHEEL_DIM_ALPHA = 60; + private static final int UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE = 48; /** - * The alpha for the increment/decrement button when it is transparent. + * The default unscaled minimal distance for a swipe to be considered a fling. */ - private static final int BUTTON_ALPHA_TRANSPARENT = 0; + private static final int UNSCALED_DEFAULT_MIN_FLING_DISTANCE = 150; /** - * The alpha for the increment/decrement button when it is opaque. + * Coefficient for adjusting touch scroll distance. */ - private static final int BUTTON_ALPHA_OPAQUE = 1; + private static final float TOUCH_SCROLL_DECELERATION_COEFFICIENT = 2.5f; /** - * The property for setting the selector paint. + * The resource id for the default layout. */ - private static final String PROPERTY_SELECTOR_PAINT_ALPHA = "selectorPaintAlpha"; - - /** - * The property for setting the increment/decrement button alpha. - */ - private static final String PROPERTY_BUTTON_ALPHA = "alpha"; + private static final int DEFAULT_LAYOUT_RESOURCE_ID = R.layout.number_picker; /** * The numbers accepted by the input text's {@link Filter} */ private static final char[] DIGIT_CHARACTERS = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' - }; + }; /** * Constant for unspecified size. @@ -215,6 +198,11 @@ public class NumberPicker extends LinearLayout { private final EditText mInputText; /** + * The distance between the two selection dividers. + */ + private final int mSelectionDividersDistance; + + /** * The min height of this widget. */ private final int mMinHeight; @@ -245,6 +233,11 @@ public class NumberPicker extends LinearLayout { private final int mTextSize; /** + * The minimal distance for a swipe to be considered a fling. + */ + private final int mMinFlingDistance; + + /** * The height of the gap between text elements if the selector wheel. */ private int mSelectorTextGapHeight; @@ -297,10 +290,7 @@ public class NumberPicker extends LinearLayout { /** * The selector indices whose value are show by the selector. */ - private final int[] mSelectorIndices = new int[] { - Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE, - Integer.MIN_VALUE - }; + private final int[] mSelectorIndices = new int[SELECTOR_WHEEL_ITEM_COUNT]; /** * The {@link Paint} for drawing the selector. @@ -343,25 +333,15 @@ public class NumberPicker extends LinearLayout { private SetSelectionCommand mSetSelectionCommand; /** - * Handle to the reusable command for adjusting the scroller. - */ - private AdjustScrollerCommand mAdjustScrollerCommand; - - /** * Handle to the reusable command for changing the current value from long * press by one. */ private ChangeCurrentByOneFromLongPressCommand mChangeCurrentByOneFromLongPressCommand; /** - * {@link Animator} for showing the up/down arrows. - */ - private final AnimatorSet mShowInputControlsAnimator; - - /** - * {@link Animator} for dimming the selector wheel. + * Command for beginning an edit of the current value via IME on long press. */ - private final Animator mDimSelectorWheelAnimator; + private BeginSoftInputOnLongPressCommand mBeginSoftInputOnLongPressCommand; /** * The Y position of the last down event. @@ -369,24 +349,14 @@ public class NumberPicker extends LinearLayout { private float mLastDownEventY; /** - * The Y position of the last motion event. + * The time of the last down event. */ - private float mLastMotionEventY; + private long mLastDownEventTime; /** - * Flag if to check for double tap and potentially start edit. + * The Y position of the last down or move event. */ - private boolean mCheckBeginEditOnUpEvent; - - /** - * Flag if to adjust the selector wheel on next up event. - */ - private boolean mAdjustScrollerOnUpEvent; - - /** - * The state of the selector wheel. - */ - private int mSelectorWheelState; + private float mLastDownOrMoveEventY; /** * Determines speed during touch scrolling. @@ -419,9 +389,9 @@ public class NumberPicker extends LinearLayout { private final int mSolidColor; /** - * Flag indicating if this widget supports flinging. + * Flag whether this widget has a selector wheel. */ - private final boolean mFlingable; + private final boolean mHasSelectorWheel; /** * Divider for showing item to be selected while scrolling @@ -434,29 +404,40 @@ public class NumberPicker extends LinearLayout { private final int mSelectionDividerHeight; /** - * Reusable {@link Rect} instance. + * The current scroll state of the number picker. */ - private final Rect mTempRect = new Rect(); + private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE; /** - * The current scroll state of the number picker. + * Flag whether to ignore move events - we ignore such when we show in IME + * to prevent the content from scrolling. */ - private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE; + private boolean mIngonreMoveEvents; /** - * The duration of the animation for showing the input controls. + * Flag whether to show soft input on tap. */ - private final long mShowInputControlsAnimimationDuration; + private boolean mShowSoftInputOnTap; /** - * Flag whether the scoll wheel and the fading edges have been initialized. + * The top of the top selection divider. */ - private boolean mScrollWheelAndFadingEdgesInitialized; + private int mTopSelectionDividerTop; /** - * The time of the last up event. + * The bottom of the bottom selection divider. */ - private long mLastUpEventTimeMillis; + private int mBottomSelectionDividerBottom; + + /** + * The virtual id of the last hovered child. + */ + private int mLastHoveredChildVirtualViewId; + + /** + * Provider to report to clients the semantic structure of this widget. + */ + private AccessibilityNodeProviderImpl mAccessibilityNodeProvider; /** * Interface to listen for changes of the current value. @@ -484,7 +465,7 @@ public class NumberPicker extends LinearLayout { public static int SCROLL_STATE_IDLE = 0; /** - * The user is scrolling using touch, and their finger is still on the screen. + * The user is scrolling using touch, and his finger is still on the screen. */ public static int SCROLL_STATE_TOUCH_SCROLL = 1; @@ -549,58 +530,78 @@ public class NumberPicker extends LinearLayout { super(context, attrs, defStyle); // process style attributes - TypedArray attributesArray = context.obtainStyledAttributes(attrs, - R.styleable.NumberPicker, defStyle, 0); + TypedArray attributesArray = context.obtainStyledAttributes( + attrs, R.styleable.NumberPicker, defStyle, 0); + final int layoutResId = attributesArray.getResourceId( + R.styleable.NumberPicker_internalLayout, DEFAULT_LAYOUT_RESOURCE_ID); + + mHasSelectorWheel = (layoutResId != DEFAULT_LAYOUT_RESOURCE_ID); + mSolidColor = attributesArray.getColor(R.styleable.NumberPicker_solidColor, 0); - mFlingable = attributesArray.getBoolean(R.styleable.NumberPicker_flingable, true); + mSelectionDivider = attributesArray.getDrawable(R.styleable.NumberPicker_selectionDivider); - int defSelectionDividerHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT, + + final int defSelectionDividerHeight = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT, getResources().getDisplayMetrics()); mSelectionDividerHeight = attributesArray.getDimensionPixelSize( R.styleable.NumberPicker_selectionDividerHeight, defSelectionDividerHeight); + + final int defSelectionDividerDistance = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE, + getResources().getDisplayMetrics()); + mSelectionDividersDistance = attributesArray.getDimensionPixelSize( + R.styleable.NumberPicker_selectionDividersDistance, defSelectionDividerDistance); + + final int defMinFlingDistance = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, UNSCALED_DEFAULT_MIN_FLING_DISTANCE, + getResources().getDisplayMetrics()); + mMinFlingDistance = attributesArray.getDimensionPixelSize( + R.styleable.NumberPicker_minFlingDistance, defMinFlingDistance); + mMinHeight = attributesArray.getDimensionPixelSize( R.styleable.NumberPicker_internalMinHeight, SIZE_UNSPECIFIED); + mMaxHeight = attributesArray.getDimensionPixelSize( R.styleable.NumberPicker_internalMaxHeight, SIZE_UNSPECIFIED); if (mMinHeight != SIZE_UNSPECIFIED && mMaxHeight != SIZE_UNSPECIFIED && mMinHeight > mMaxHeight) { throw new IllegalArgumentException("minHeight > maxHeight"); } - mMinWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_internalMinWidth, - SIZE_UNSPECIFIED); - mMaxWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_internalMaxWidth, - SIZE_UNSPECIFIED); + + mMinWidth = attributesArray.getDimensionPixelSize( + R.styleable.NumberPicker_internalMinWidth, SIZE_UNSPECIFIED); + + mMaxWidth = attributesArray.getDimensionPixelSize( + R.styleable.NumberPicker_internalMaxWidth, SIZE_UNSPECIFIED); if (mMinWidth != SIZE_UNSPECIFIED && mMaxWidth != SIZE_UNSPECIFIED && mMinWidth > mMaxWidth) { throw new IllegalArgumentException("minWidth > maxWidth"); } + mComputeMaxWidth = (mMaxWidth == Integer.MAX_VALUE); - attributesArray.recycle(); - mShowInputControlsAnimimationDuration = getResources().getInteger( - R.integer.config_longAnimTime); + attributesArray.recycle(); // By default Linearlayout that we extend is not drawn. This is // its draw() method is not called but dispatchDraw() is called // directly (see ViewGroup.drawChild()). However, this class uses // the fading edge effect implemented by View and we need our // draw() method to be called. Therefore, we declare we will draw. - setWillNotDraw(false); - setSelectorWheelState(SELECTOR_WHEEL_STATE_NONE); + setWillNotDraw(!mHasSelectorWheel); LayoutInflater inflater = (LayoutInflater) getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); - inflater.inflate(R.layout.number_picker, this, true); + inflater.inflate(layoutResId, this, true); OnClickListener onClickListener = new OnClickListener() { public void onClick(View v) { hideSoftInput(); mInputText.clearFocus(); if (v.getId() == R.id.increment) { - changeCurrentByOne(true); + changeValueByOne(true); } else { - changeCurrentByOne(false); + changeValueByOne(false); } } }; @@ -610,23 +611,31 @@ public class NumberPicker extends LinearLayout { hideSoftInput(); mInputText.clearFocus(); if (v.getId() == R.id.increment) { - postChangeCurrentByOneFromLongPress(true); + postChangeCurrentByOneFromLongPress(true, 0); } else { - postChangeCurrentByOneFromLongPress(false); + postChangeCurrentByOneFromLongPress(false, 0); } return true; } }; // increment button - mIncrementButton = (ImageButton) findViewById(R.id.increment); - mIncrementButton.setOnClickListener(onClickListener); - mIncrementButton.setOnLongClickListener(onLongClickListener); + if (!mHasSelectorWheel) { + mIncrementButton = (ImageButton) findViewById(R.id.increment); + mIncrementButton.setOnClickListener(onClickListener); + mIncrementButton.setOnLongClickListener(onLongClickListener); + } else { + mIncrementButton = null; + } // decrement button - mDecrementButton = (ImageButton) findViewById(R.id.decrement); - mDecrementButton.setOnClickListener(onClickListener); - mDecrementButton.setOnLongClickListener(onLongClickListener); + if (!mHasSelectorWheel) { + mDecrementButton = (ImageButton) findViewById(R.id.decrement); + mDecrementButton.setOnClickListener(onClickListener); + mDecrementButton.setOnLongClickListener(onLongClickListener); + } else { + mDecrementButton = null; + } // input text mInputText = (EditText) findViewById(R.id.numberpicker_input); @@ -648,7 +657,6 @@ public class NumberPicker extends LinearLayout { mInputText.setImeOptions(EditorInfo.IME_ACTION_DONE); // initialize constants - mTouchSlop = ViewConfiguration.getTapTimeout(); ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = configuration.getScaledTouchSlop(); mMinimumFlingVelocity = configuration.getScaledMinimumFlingVelocity(); @@ -667,69 +675,22 @@ public class NumberPicker extends LinearLayout { paint.setColor(color); mSelectorWheelPaint = paint; - // create the animator for showing the input controls - mDimSelectorWheelAnimator = ObjectAnimator.ofInt(this, PROPERTY_SELECTOR_PAINT_ALPHA, - SELECTOR_WHEEL_BRIGHT_ALPHA, SELECTOR_WHEEL_DIM_ALPHA); - final ObjectAnimator showIncrementButton = ObjectAnimator.ofFloat(mIncrementButton, - PROPERTY_BUTTON_ALPHA, BUTTON_ALPHA_TRANSPARENT, BUTTON_ALPHA_OPAQUE); - final ObjectAnimator showDecrementButton = ObjectAnimator.ofFloat(mDecrementButton, - PROPERTY_BUTTON_ALPHA, BUTTON_ALPHA_TRANSPARENT, BUTTON_ALPHA_OPAQUE); - mShowInputControlsAnimator = new AnimatorSet(); - mShowInputControlsAnimator.playTogether(mDimSelectorWheelAnimator, showIncrementButton, - showDecrementButton); - mShowInputControlsAnimator.addListener(new AnimatorListenerAdapter() { - private boolean mCanceled = false; - - @Override - public void onAnimationEnd(Animator animation) { - if (!mCanceled) { - // if canceled => we still want the wheel drawn - setSelectorWheelState(SELECTOR_WHEEL_STATE_SMALL); - } - mCanceled = false; - } - - @Override - public void onAnimationCancel(Animator animation) { - if (mShowInputControlsAnimator.isRunning()) { - mCanceled = true; - } - } - }); - // create the fling and adjust scrollers mFlingScroller = new Scroller(getContext(), null, true); mAdjustScroller = new Scroller(getContext(), new DecelerateInterpolator(2.5f)); updateInputTextView(); - updateIncrementAndDecrementButtonsVisibilityState(); - - if (mFlingable) { - if (isInEditMode()) { - setSelectorWheelState(SELECTOR_WHEEL_STATE_SMALL); - } else { - // Start with shown selector wheel and hidden controls. When made - // visible hide the selector and fade-in the controls to suggest - // fling interaction. - setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE); - hideInputControls(); - } - } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (!mHasSelectorWheel) { + super.onLayout(changed, left, top, right, bottom); + return; + } final int msrdWdth = getMeasuredWidth(); final int msrdHght = getMeasuredHeight(); - // Increment button at the top. - final int inctBtnMsrdWdth = mIncrementButton.getMeasuredWidth(); - final int incrBtnLeft = (msrdWdth - inctBtnMsrdWdth) / 2; - final int incrBtnTop = 0; - final int incrBtnRight = incrBtnLeft + inctBtnMsrdWdth; - final int incrBtnBottom = incrBtnTop + mIncrementButton.getMeasuredHeight(); - mIncrementButton.layout(incrBtnLeft, incrBtnTop, incrBtnRight, incrBtnBottom); - // Input text centered horizontally. final int inptTxtMsrdWdth = mInputText.getMeasuredWidth(); final int inptTxtMsrdHght = mInputText.getMeasuredHeight(); @@ -739,24 +700,23 @@ public class NumberPicker extends LinearLayout { final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght; mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom); - // Decrement button at the top. - final int decrBtnMsrdWdth = mIncrementButton.getMeasuredWidth(); - final int decrBtnLeft = (msrdWdth - decrBtnMsrdWdth) / 2; - final int decrBtnTop = msrdHght - mDecrementButton.getMeasuredHeight(); - final int decrBtnRight = decrBtnLeft + decrBtnMsrdWdth; - final int decrBtnBottom = msrdHght; - mDecrementButton.layout(decrBtnLeft, decrBtnTop, decrBtnRight, decrBtnBottom); - - if (!mScrollWheelAndFadingEdgesInitialized) { - mScrollWheelAndFadingEdgesInitialized = true; + if (changed) { // need to do all this when we know our size initializeSelectorWheel(); initializeFadingEdges(); + mTopSelectionDividerTop = (getHeight() - mSelectionDividersDistance) / 2 + - mSelectionDividerHeight; + mBottomSelectionDividerBottom = mTopSelectionDividerTop + 2 * mSelectionDividerHeight + + mSelectionDividersDistance; } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (!mHasSelectorWheel) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + return; + } // Try greedily to fit the max width and height. final int newWidthMeasureSpec = makeMeasureSpec(widthMeasureSpec, mMaxWidth); final int newHeightMeasureSpec = makeMeasureSpec(heightMeasureSpec, mMaxHeight); @@ -769,120 +729,143 @@ public class NumberPicker extends LinearLayout { setMeasuredDimension(widthSize, heightSize); } + /** + * Move to the final position of a scroller. Ensures to force finish the scroller + * and if it is not at its final position a scroll of the selector wheel is + * performed to fast forward to the final position. + * + * @param scroller The scroller to whose final position to get. + * @return True of the a move was performed, i.e. the scroller was not in final position. + */ + private boolean moveToFinalScrollerPosition(Scroller scroller) { + scroller.forceFinished(true); + int amountToScroll = scroller.getFinalY() - scroller.getCurrY(); + int futureScrollOffset = (mCurrentScrollOffset + amountToScroll) % mSelectorElementHeight; + int overshootAdjustment = mInitialScrollOffset - futureScrollOffset; + if (overshootAdjustment != 0) { + if (Math.abs(overshootAdjustment) > mSelectorElementHeight / 2) { + if (overshootAdjustment > 0) { + overshootAdjustment -= mSelectorElementHeight; + } else { + overshootAdjustment += mSelectorElementHeight; + } + } + amountToScroll += overshootAdjustment; + scrollBy(0, amountToScroll); + return true; + } + return false; + } + @Override public boolean onInterceptTouchEvent(MotionEvent event) { - if (!isEnabled() || !mFlingable) { + if (!mHasSelectorWheel || !isEnabled()) { return false; } - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - mLastMotionEventY = mLastDownEventY = event.getY(); + final int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_DOWN: { removeAllCallbacks(); - mShowInputControlsAnimator.cancel(); - mDimSelectorWheelAnimator.cancel(); - mCheckBeginEditOnUpEvent = false; - mAdjustScrollerOnUpEvent = true; - if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) { - mSelectorWheelPaint.setAlpha(SELECTOR_WHEEL_BRIGHT_ALPHA); - boolean scrollersFinished = mFlingScroller.isFinished() - && mAdjustScroller.isFinished(); - if (!scrollersFinished) { - mFlingScroller.forceFinished(true); - mAdjustScroller.forceFinished(true); - onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); - } - mCheckBeginEditOnUpEvent = scrollersFinished; - mAdjustScrollerOnUpEvent = true; + mInputText.setVisibility(View.INVISIBLE); + mLastDownOrMoveEventY = mLastDownEventY = event.getY(); + mLastDownEventTime = event.getEventTime(); + mIngonreMoveEvents = false; + mShowSoftInputOnTap = false; + if (!mFlingScroller.isFinished()) { + mFlingScroller.forceFinished(true); + mAdjustScroller.forceFinished(true); + onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); + } else if (!mAdjustScroller.isFinished()) { + mFlingScroller.forceFinished(true); + mAdjustScroller.forceFinished(true); + } else if (mLastDownEventY < mTopSelectionDividerTop) { hideSoftInput(); - hideInputControls(); - return true; - } - if (isEventInVisibleViewHitRect(event, mIncrementButton) - || isEventInVisibleViewHitRect(event, mDecrementButton)) { - return false; - } - mAdjustScrollerOnUpEvent = false; - setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE); - hideSoftInput(); - hideInputControls(); - return true; - case MotionEvent.ACTION_MOVE: - float currentMoveY = event.getY(); - int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY); - if (deltaDownY > mTouchSlop) { - mCheckBeginEditOnUpEvent = false; - onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); - setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE); + postChangeCurrentByOneFromLongPress( + false, ViewConfiguration.getLongPressTimeout()); + } else if (mLastDownEventY > mBottomSelectionDividerBottom) { hideSoftInput(); - hideInputControls(); - return true; + postChangeCurrentByOneFromLongPress( + true, ViewConfiguration.getLongPressTimeout()); + } else { + mShowSoftInputOnTap = true; + postBeginSoftInputOnLongPressCommand(); } - break; + return true; + } } return false; } @Override - public boolean onTouchEvent(MotionEvent ev) { - if (!isEnabled()) { + public boolean onTouchEvent(MotionEvent event) { + if (!isEnabled() || !mHasSelectorWheel) { return false; } if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } - mVelocityTracker.addMovement(ev); - int action = ev.getActionMasked(); + mVelocityTracker.addMovement(event); + int action = event.getActionMasked(); switch (action) { - case MotionEvent.ACTION_MOVE: - float currentMoveY = ev.getY(); - if (mCheckBeginEditOnUpEvent - || mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { + case MotionEvent.ACTION_MOVE: { + if (mIngonreMoveEvents) { + break; + } + float currentMoveY = event.getY(); + if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY); if (deltaDownY > mTouchSlop) { - mCheckBeginEditOnUpEvent = false; + removeAllCallbacks(); onScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); } + } else { + int deltaMoveY = (int) ((currentMoveY - mLastDownOrMoveEventY) + / TOUCH_SCROLL_DECELERATION_COEFFICIENT); + scrollBy(0, deltaMoveY); + invalidate(); } - int deltaMoveY = (int) (currentMoveY - mLastMotionEventY); - scrollBy(0, deltaMoveY); - invalidate(); - mLastMotionEventY = currentMoveY; - break; - case MotionEvent.ACTION_UP: - if (mCheckBeginEditOnUpEvent) { - mCheckBeginEditOnUpEvent = false; - final long deltaTapTimeMillis = ev.getEventTime() - mLastUpEventTimeMillis; - if (deltaTapTimeMillis < ViewConfiguration.getDoubleTapTimeout()) { - setSelectorWheelState(SELECTOR_WHEEL_STATE_SMALL); - showInputControls(mShowInputControlsAnimimationDuration); - mInputText.requestFocus(); - InputMethodManager inputMethodManager = InputMethodManager.peekInstance(); - if (inputMethodManager != null) { - inputMethodManager.showSoftInput(mInputText, 0); - } - mLastUpEventTimeMillis = ev.getEventTime(); - return true; - } - } + mLastDownOrMoveEventY = currentMoveY; + } break; + case MotionEvent.ACTION_UP: { + removeBeginSoftInputCommand(); + removeChangeCurrentByOneFromLongPress(); VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); int initialVelocity = (int) velocityTracker.getYVelocity(); if (Math.abs(initialVelocity) > mMinimumFlingVelocity) { - fling(initialVelocity); + int deltaMove = (int) (event.getY() - mLastDownEventY); + int absDeltaMoveY = Math.abs(deltaMove); + if (absDeltaMoveY > mMinFlingDistance) { + fling(initialVelocity); + } else { + changeValueByOne(deltaMove < 0); + } onScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); } else { - if (mAdjustScrollerOnUpEvent) { - if (mFlingScroller.isFinished() && mAdjustScroller.isFinished()) { - postAdjustScrollerCommand(0); + int eventY = (int) event.getY(); + int deltaMoveY = (int) Math.abs(eventY - mLastDownEventY); + long deltaTime = event.getEventTime() - mLastDownEventTime; + if (deltaMoveY <= mTouchSlop && deltaTime < ViewConfiguration.getTapTimeout()) { + if (mShowSoftInputOnTap) { + mShowSoftInputOnTap = false; + showSoftInput(); + } else { + int selectorIndexOffset = (eventY / mSelectorElementHeight) + - SELECTOR_MIDDLE_ITEM_INDEX; + if (selectorIndexOffset > 0) { + changeValueByOne(true); + } else if (selectorIndexOffset < 0) { + changeValueByOne(false); + } } } else { - postAdjustScrollerCommand(SHOW_INPUT_CONTROLS_DELAY_MILLIS); + ensureScrollWheelAdjusted(); } + onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } mVelocityTracker.recycle(); mVelocityTracker = null; - mLastUpEventTimeMillis = ev.getEventTime(); - break; + } break; } return true; } @@ -891,12 +874,6 @@ public class NumberPicker extends LinearLayout { public boolean dispatchTouchEvent(MotionEvent event) { final int action = event.getActionMasked(); switch (action) { - case MotionEvent.ACTION_MOVE: - if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) { - removeAllCallbacks(); - forceCompleteChangeCurrentByOneViaScroll(); - } - break; case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: removeAllCallbacks(); @@ -907,27 +884,75 @@ public class NumberPicker extends LinearLayout { @Override public boolean dispatchKeyEvent(KeyEvent event) { - int keyCode = event.getKeyCode(); - if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) { - removeAllCallbacks(); + final int keyCode = event.getKeyCode(); + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + removeAllCallbacks(); + break; } return super.dispatchKeyEvent(event); } @Override public boolean dispatchTrackballEvent(MotionEvent event) { - int action = event.getActionMasked(); - if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { - removeAllCallbacks(); + final int action = event.getActionMasked(); + switch (action) { + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + removeAllCallbacks(); + break; } return super.dispatchTrackballEvent(event); } @Override - public void computeScroll() { - if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) { - return; + protected boolean dispatchHoverEvent(MotionEvent event) { + if (!mHasSelectorWheel) { + return super.dispatchHoverEvent(event); + } + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + final int eventY = (int) event.getY(); + final int hoveredVirtualViewId; + if (eventY < mTopSelectionDividerTop) { + hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_DECREMENT; + } else if (eventY > mBottomSelectionDividerBottom) { + hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INCREMENT; + } else { + hoveredVirtualViewId = AccessibilityNodeProviderImpl.VIRTUAL_VIEW_ID_INPUT; + } + final int action = event.getActionMasked(); + AccessibilityNodeProviderImpl provider = + (AccessibilityNodeProviderImpl) getAccessibilityNodeProvider(); + switch (action) { + case MotionEvent.ACTION_HOVER_ENTER: { + provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId, + AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); + mLastHoveredChildVirtualViewId = hoveredVirtualViewId; + } break; + case MotionEvent.ACTION_HOVER_MOVE: { + if (mLastHoveredChildVirtualViewId != hoveredVirtualViewId + && mLastHoveredChildVirtualViewId != View.NO_ID) { + provider.sendAccessibilityEventForVirtualView( + mLastHoveredChildVirtualViewId, + AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); + provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId, + AccessibilityEvent.TYPE_VIEW_HOVER_ENTER); + mLastHoveredChildVirtualViewId = hoveredVirtualViewId; + } + } break; + case MotionEvent.ACTION_HOVER_EXIT: { + provider.sendAccessibilityEventForVirtualView(hoveredVirtualViewId, + AccessibilityEvent.TYPE_VIEW_HOVER_EXIT); + mLastHoveredChildVirtualViewId = View.NO_ID; + } break; + } } + return false; + } + + @Override + public void computeScroll() { Scroller scroller = mFlingScroller; if (scroller.isFinished()) { scroller = mAdjustScroller; @@ -952,16 +977,17 @@ public class NumberPicker extends LinearLayout { @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); - mIncrementButton.setEnabled(enabled); - mDecrementButton.setEnabled(enabled); + if (!mHasSelectorWheel) { + mIncrementButton.setEnabled(enabled); + } + if (!mHasSelectorWheel) { + mDecrementButton.setEnabled(enabled); + } mInputText.setEnabled(enabled); } @Override public void scrollBy(int x, int y) { - if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) { - return; - } int[] selectorIndices = mSelectorIndices; if (!mWrapSelectorWheel && y > 0 && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) { @@ -977,7 +1003,7 @@ public class NumberPicker extends LinearLayout { while (mCurrentScrollOffset - mInitialScrollOffset > mSelectorTextGapHeight) { mCurrentScrollOffset -= mSelectorElementHeight; decrementSelectorIndices(selectorIndices); - changeCurrent(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX]); + setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true); if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] <= mMinValue) { mCurrentScrollOffset = mInitialScrollOffset; } @@ -985,7 +1011,7 @@ public class NumberPicker extends LinearLayout { while (mCurrentScrollOffset - mInitialScrollOffset < -mSelectorTextGapHeight) { mCurrentScrollOffset += mSelectorElementHeight; incrementSelectorIndices(selectorIndices); - changeCurrent(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX]); + setValueInternal(selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX], true); if (!mWrapSelectorWheel && selectorIndices[SELECTOR_MIDDLE_ITEM_INDEX] >= mMaxValue) { mCurrentScrollOffset = mInitialScrollOffset; } @@ -1024,8 +1050,7 @@ public class NumberPicker extends LinearLayout { * * @param formatter The formatter object. If formatter is <code>null</code>, * {@link String#valueOf(int)} will be used. - * - * @see #setDisplayedValues(String[]) + *@see #setDisplayedValues(String[]) */ public void setFormatter(Formatter formatter) { if (formatter == mFormatter) { @@ -1068,26 +1093,35 @@ public class NumberPicker extends LinearLayout { if (mValue == value) { return; } - if (value < mMinValue) { - value = mWrapSelectorWheel ? mMaxValue : mMinValue; - } - if (value > mMaxValue) { - value = mWrapSelectorWheel ? mMinValue : mMaxValue; - } - mValue = value; + setValueInternal(value, false); initializeSelectorWheelIndices(); - updateInputTextView(); - updateIncrementAndDecrementButtonsVisibilityState(); invalidate(); } /** - * Hides the soft input of it is active for the input text. + * Shows the soft input for its input text. + */ + private void showSoftInput() { + InputMethodManager inputMethodManager = InputMethodManager.peekInstance(); + if (inputMethodManager != null) { + if (mHasSelectorWheel) { + mInputText.setVisibility(View.VISIBLE); + } + mInputText.requestFocus(); + inputMethodManager.showSoftInput(mInputText, 0); + } + } + + /** + * Hides the soft input if it is active for the input text. */ private void hideSoftInput() { InputMethodManager inputMethodManager = InputMethodManager.peekInstance(); if (inputMethodManager != null && inputMethodManager.isActive(mInputText)) { inputMethodManager.hideSoftInputFromWindow(getWindowToken(), 0); + if (mHasSelectorWheel) { + mInputText.setVisibility(View.INVISIBLE); + } } } @@ -1151,23 +1185,22 @@ public class NumberPicker extends LinearLayout { * wrap around the {@link NumberPicker#getMinValue()} and * {@link NumberPicker#getMaxValue()} values. * <p> - * By default if the range (max - min) is more than five (the number of - * items shown on the selector wheel) the selector wheel wrapping is - * enabled. + * By default if the range (max - min) is more than the number of items shown + * on the selector wheel the selector wheel wrapping is enabled. * </p> * <p> - * <strong>Note:</strong> If the number of items, i.e. the range - * ({@link #getMaxValue()} - {@link #getMinValue()}) is less than - * {@link #SELECTOR_WHEEL_ITEM_COUNT}, the selector wheel will not - * wrap. Hence, in such a case calling this method is a NOP. + * <strong>Note:</strong> If the number of items, i.e. the range ( + * {@link #getMaxValue()} - {@link #getMinValue()}) is less than + * the number of items shown on the selector wheel, the selector wheel will + * not wrap. Hence, in such a case calling this method is a NOP. * </p> + * * @param wrapSelectorWheel Whether to wrap. */ public void setWrapSelectorWheel(boolean wrapSelectorWheel) { final boolean wrappingAllowed = (mMaxValue - mMinValue) >= mSelectorIndices.length; if ((!wrapSelectorWheel || wrappingAllowed) && wrapSelectorWheel != mWrapSelectorWheel) { mWrapSelectorWheel = wrapSelectorWheel; - updateIncrementAndDecrementButtonsVisibilityState(); } } @@ -1224,6 +1257,7 @@ public class NumberPicker extends LinearLayout { initializeSelectorWheelIndices(); updateInputTextView(); tryComputeMaxWidth(); + invalidate(); } /** @@ -1256,6 +1290,7 @@ public class NumberPicker extends LinearLayout { initializeSelectorWheelIndices(); updateInputTextView(); tryComputeMaxWidth(); + invalidate(); } /** @@ -1300,102 +1335,49 @@ public class NumberPicker extends LinearLayout { } @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - // make sure we show the controls only the very - // first time the user sees this widget - if (mFlingable && !isInEditMode()) { - // animate a bit slower the very first time - showInputControls(mShowInputControlsAnimimationDuration * 2); - } - } - - @Override protected void onDetachedFromWindow() { removeAllCallbacks(); } @Override - protected void dispatchDraw(Canvas canvas) { - // There is a good reason for doing this. See comments in draw(). - } - - @Override - public void draw(Canvas canvas) { - // Dispatch draw to our children only if we are not currently running - // the animation for simultaneously dimming the scroll wheel and - // showing in the buttons. This class takes advantage of the View - // implementation of fading edges effect to draw the selector wheel. - // However, in View.draw(), the fading is applied after all the children - // have been drawn and we do not want this fading to be applied to the - // buttons. Therefore, we draw our children after we have completed - // drawing ourselves. - super.draw(canvas); - - // Draw our children if we are not showing the selector wheel of fading - // it out - if (mShowInputControlsAnimator.isRunning() - || mSelectorWheelState != SELECTOR_WHEEL_STATE_LARGE) { - long drawTime = getDrawingTime(); - for (int i = 0, count = getChildCount(); i < count; i++) { - View child = getChildAt(i); - if (!child.isShown()) { - continue; - } - drawChild(canvas, getChildAt(i), drawTime); - } - } - } - - @Override protected void onDraw(Canvas canvas) { - if (mSelectorWheelState == SELECTOR_WHEEL_STATE_NONE) { + if (!mHasSelectorWheel) { + super.onDraw(canvas); return; } - float x = (mRight - mLeft) / 2; float y = mCurrentScrollOffset; - final int restoreCount = canvas.save(); - - if (mSelectorWheelState == SELECTOR_WHEEL_STATE_SMALL) { - Rect clipBounds = canvas.getClipBounds(); - clipBounds.inset(0, mSelectorElementHeight); - canvas.clipRect(clipBounds); - } - // draw the selector wheel int[] selectorIndices = mSelectorIndices; for (int i = 0; i < selectorIndices.length; i++) { int selectorIndex = selectorIndices[i]; String scrollSelectorValue = mSelectorIndexToStringCache.get(selectorIndex); - // Do not draw the middle item if input is visible since the input is shown only - // if the wheel is static and it covers the middle item. Otherwise, if the user - // starts editing the text via the IME he may see a dimmed version of the old - // value intermixed with the new one. + // Do not draw the middle item if input is visible since the input + // is shown only if the wheel is static and it covers the middle + // item. Otherwise, if the user starts editing the text via the + // IME he may see a dimmed version of the old value intermixed + // with the new one. if (i != SELECTOR_MIDDLE_ITEM_INDEX || mInputText.getVisibility() != VISIBLE) { canvas.drawText(scrollSelectorValue, x, y, mSelectorWheelPaint); } y += mSelectorElementHeight; } - // draw the selection dividers (only if scrolling and drawable specified) + // draw the selection dividers if (mSelectionDivider != null) { // draw the top divider - int topOfTopDivider = - (getHeight() - mSelectorElementHeight - mSelectionDividerHeight) / 2; + int topOfTopDivider = mTopSelectionDividerTop; int bottomOfTopDivider = topOfTopDivider + mSelectionDividerHeight; mSelectionDivider.setBounds(0, topOfTopDivider, mRight, bottomOfTopDivider); mSelectionDivider.draw(canvas); // draw the bottom divider - int topOfBottomDivider = topOfTopDivider + mSelectorElementHeight; - int bottomOfBottomDivider = bottomOfTopDivider + mSelectorElementHeight; + int bottomOfBottomDivider = mBottomSelectionDividerBottom; + int topOfBottomDivider = bottomOfBottomDivider - mSelectionDividerHeight; mSelectionDivider.setBounds(0, topOfBottomDivider, mRight, bottomOfBottomDivider); mSelectionDivider.draw(canvas); } - - canvas.restoreToCount(restoreCount); } @Override @@ -1408,12 +1390,20 @@ public class NumberPicker extends LinearLayout { public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(NumberPicker.class.getName()); + event.setScrollable(true); + event.setScrollY((mMinValue + mValue) * mSelectorElementHeight); + event.setMaxScrollY((mMaxValue - mMinValue) * mSelectorElementHeight); } @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - info.setClassName(NumberPicker.class.getName()); + public AccessibilityNodeProvider getAccessibilityNodeProvider() { + if (!mHasSelectorWheel) { + return super.getAccessibilityNodeProvider(); + } + if (mAccessibilityNodeProvider == null) { + mAccessibilityNodeProvider = new AccessibilityNodeProviderImpl(); + } + return mAccessibilityNodeProvider; } /** @@ -1442,17 +1432,17 @@ public class NumberPicker extends LinearLayout { } /** - * Utility to reconcile a desired size and state, with constraints imposed by - * a MeasureSpec. Tries to respect the min size, unless a different size is - * imposed by the constraints. + * Utility to reconcile a desired size and state, with constraints imposed + * by a MeasureSpec. Tries to respect the min size, unless a different size + * is imposed by the constraints. * * @param minSize The minimal desired size. * @param measuredSize The currently measured size. * @param measureSpec The current measure spec. * @return The resolved size and state. */ - private int resolveSizeAndStateRespectingMinSize(int minSize, int measuredSize, - int measureSpec) { + private int resolveSizeAndStateRespectingMinSize( + int minSize, int measuredSize, int measureSpec) { if (minSize != SIZE_UNSPECIFIED) { final int desiredWidth = Math.max(minSize, measuredSize); return resolveSizeAndState(desiredWidth, measureSpec, 0); @@ -1462,8 +1452,8 @@ public class NumberPicker extends LinearLayout { } /** - * Resets the selector indices and clear the cached - * string representation of these indices. + * Resets the selector indices and clear the cached string representation of + * these indices. */ private void initializeSelectorWheelIndices() { mSelectorIndexToStringCache.clear(); @@ -1480,39 +1470,44 @@ public class NumberPicker extends LinearLayout { } /** - * Sets the current value of this NumberPicker, and sets mPrevious to the - * previous value. If current is greater than mEnd less than mStart, the - * value of mCurrent is wrapped around. Subclasses can override this to - * change the wrapping behavior + * Sets the current value of this NumberPicker. * - * @param current the new value of the NumberPicker + * @param current The new value of the NumberPicker. + * @param notifyChange Whether to notify if the current value changed. */ - private void changeCurrent(int current) { + private void setValueInternal(int current, boolean notifyChange) { if (mValue == current) { return; } // Wrap around the values if we go past the start or end if (mWrapSelectorWheel) { current = getWrappedSelectorIndex(current); + } else { + current = Math.max(current, mMinValue); + current = Math.min(current, mMaxValue); } int previous = mValue; - setValue(current); - notifyChange(previous, current); + mValue = current; + updateInputTextView(); + if (notifyChange) { + notifyChange(previous, current); + } } /** * Changes the current value by one which is increment or * decrement based on the passes argument. + * decrement the current value. * * @param increment True to increment, false to decrement. */ - private void changeCurrentByOne(boolean increment) { - if (mFlingable) { - mDimSelectorWheelAnimator.cancel(); + private void changeValueByOne(boolean increment) { + if (mHasSelectorWheel) { mInputText.setVisibility(View.INVISIBLE); - mSelectorWheelPaint.setAlpha(SELECTOR_WHEEL_BRIGHT_ALPHA); + if (!moveToFinalScrollerPosition(mFlingScroller)) { + moveToFinalScrollerPosition(mAdjustScroller); + } mPreviousScrollerY = 0; - forceCompleteChangeCurrentByOneViaScroll(); if (increment) { mFlingScroller.startScroll(0, 0, 0, -mSelectorElementHeight, CHANGE_CURRENT_BY_ONE_SCROLL_DURATION); @@ -1523,81 +1518,26 @@ public class NumberPicker extends LinearLayout { invalidate(); } else { if (increment) { - changeCurrent(mValue + 1); + setValueInternal(mValue + 1, true); } else { - changeCurrent(mValue - 1); + setValueInternal(mValue - 1, true); } } } - /** - * Ensures that if we are in the process of changing the current value - * by one via scrolling the scroller gets to its final state and the - * value is updated. - */ - private void forceCompleteChangeCurrentByOneViaScroll() { - Scroller scroller = mFlingScroller; - if (!scroller.isFinished()) { - final int yBeforeAbort = scroller.getCurrY(); - scroller.abortAnimation(); - final int yDelta = scroller.getCurrY() - yBeforeAbort; - scrollBy(0, yDelta); - } - } - - /** - * Sets the <code>alpha</code> of the {@link Paint} for drawing the selector - * wheel. - */ - @SuppressWarnings("unused") - // Called via reflection - private void setSelectorPaintAlpha(int alpha) { - mSelectorWheelPaint.setAlpha(alpha); - invalidate(); - } - - /** - * @return If the <code>event</code> is in the visible <code>view</code>. - */ - private boolean isEventInVisibleViewHitRect(MotionEvent event, View view) { - if (view.getVisibility() == VISIBLE) { - view.getHitRect(mTempRect); - return mTempRect.contains((int) event.getX(), (int) event.getY()); - } - return false; - } - - /** - * Sets the <code>selectorWheelState</code>. - */ - private void setSelectorWheelState(int selectorWheelState) { - mSelectorWheelState = selectorWheelState; - if (selectorWheelState == SELECTOR_WHEEL_STATE_LARGE) { - mSelectorWheelPaint.setAlpha(SELECTOR_WHEEL_BRIGHT_ALPHA); - } - - if (mFlingable && selectorWheelState == SELECTOR_WHEEL_STATE_LARGE - && AccessibilityManager.getInstance(mContext).isEnabled()) { - AccessibilityManager.getInstance(mContext).interrupt(); - String text = mContext.getString(R.string.number_picker_increment_scroll_action); - mInputText.setContentDescription(text); - mInputText.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); - mInputText.setContentDescription(null); - } - } - private void initializeSelectorWheel() { initializeSelectorWheelIndices(); int[] selectorIndices = mSelectorIndices; int totalTextHeight = selectorIndices.length * mTextSize; float totalTextGapHeight = (mBottom - mTop) - totalTextHeight; - float textGapCount = selectorIndices.length - 1; + float textGapCount = selectorIndices.length; mSelectorTextGapHeight = (int) (totalTextGapHeight / textGapCount + 0.5f); mSelectorElementHeight = mTextSize + mSelectorTextGapHeight; - // Ensure that the middle item is positioned the same as the text in mInputText + // Ensure that the middle item is positioned the same as the text in + // mInputText int editTextTextPosition = mInputText.getBaseline() + mInputText.getTop(); - mInitialScrollOffset = editTextTextPosition - - (mSelectorElementHeight * SELECTOR_MIDDLE_ITEM_INDEX); + mInitialScrollOffset = editTextTextPosition + - (mSelectorElementHeight * SELECTOR_MIDDLE_ITEM_INDEX); mCurrentScrollOffset = mInitialScrollOffset; updateInputTextView(); } @@ -1612,16 +1552,14 @@ public class NumberPicker extends LinearLayout { */ private void onScrollerFinished(Scroller scroller) { if (scroller == mFlingScroller) { - if (mSelectorWheelState == SELECTOR_WHEEL_STATE_LARGE) { - postAdjustScrollerCommand(0); - onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); - } else { + if (!ensureScrollWheelAdjusted()) { updateInputTextView(); - fadeSelectorWheel(mShowInputControlsAnimimationDuration); } + onScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } else { - updateInputTextView(); - showInputControls(mShowInputControlsAnimimationDuration); + if (mScrollState != OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { + updateInputTextView(); + } } } @@ -1654,56 +1592,6 @@ public class NumberPicker extends LinearLayout { } /** - * Hides the input controls which is the up/down arrows and the text field. - */ - private void hideInputControls() { - mShowInputControlsAnimator.cancel(); - mIncrementButton.setVisibility(INVISIBLE); - mDecrementButton.setVisibility(INVISIBLE); - mInputText.setVisibility(INVISIBLE); - } - - /** - * Show the input controls by making them visible and animating the alpha - * property up/down arrows. - * - * @param animationDuration The duration of the animation. - */ - private void showInputControls(long animationDuration) { - updateIncrementAndDecrementButtonsVisibilityState(); - mInputText.setVisibility(VISIBLE); - mShowInputControlsAnimator.setDuration(animationDuration); - mShowInputControlsAnimator.start(); - } - - /** - * Fade the selector wheel via an animation. - * - * @param animationDuration The duration of the animation. - */ - private void fadeSelectorWheel(long animationDuration) { - mInputText.setVisibility(VISIBLE); - mDimSelectorWheelAnimator.setDuration(animationDuration); - mDimSelectorWheelAnimator.start(); - } - - /** - * Updates the visibility state of the increment and decrement buttons. - */ - private void updateIncrementAndDecrementButtonsVisibilityState() { - if (mWrapSelectorWheel || mValue < mMaxValue) { - mIncrementButton.setVisibility(VISIBLE); - } else { - mIncrementButton.setVisibility(INVISIBLE); - } - if (mWrapSelectorWheel || mValue > mMinValue) { - mDecrementButton.setVisibility(VISIBLE); - } else { - mDecrementButton.setVisibility(INVISIBLE); - } - } - - /** * @return The wrapped index <code>selectorIndex</code> value. */ private int getWrappedSelectorIndex(int selectorIndex) { @@ -1749,8 +1637,7 @@ public class NumberPicker extends LinearLayout { /** * Ensures we have a cached string representation of the given <code> - * selectorIndex</code> - * to avoid multiple instantiations of the same string. + * selectorIndex</code> to avoid multiple instantiations of the same string. */ private void ensureCachedScrollSelectorValue(int selectorIndex) { SparseArray<String> cache = mSelectorIndexToStringCache; @@ -1783,7 +1670,7 @@ public class NumberPicker extends LinearLayout { } else { // Check the new value and ensure it's in range int current = getSelectedPos(str.toString()); - changeCurrent(current); + setValueInternal(current, true); } } @@ -1792,25 +1679,23 @@ public class NumberPicker extends LinearLayout { * the string corresponding to the index specified by the current value will * be returned. Otherwise, the formatter specified in {@link #setFormatter} * will be used to format the number. + * + * @return Whether the text was updated. */ - private void updateInputTextView() { + private boolean updateInputTextView() { /* * If we don't have displayed values then use the current number else * find the correct value in the displayed values for the current * number. */ - if (mDisplayedValues == null) { - mInputText.setText(formatNumber(mValue)); - } else { - mInputText.setText(mDisplayedValues[mValue - mMinValue]); + String text = (mDisplayedValues == null) ? formatNumber(mValue) + : mDisplayedValues[mValue - mMinValue]; + if (!TextUtils.isEmpty(text) && !text.equals(mInputText.getText().toString())) { + mInputText.setText(text); + return true; } - mInputText.setSelection(mInputText.getText().length()); - if (mFlingable && AccessibilityManager.getInstance(mContext).isEnabled()) { - String text = mContext.getString(R.string.number_picker_increment_scroll_mode, - mInputText.getText()); - mInputText.setContentDescription(text); - } + return false; } /** @@ -1828,14 +1713,45 @@ public class NumberPicker extends LinearLayout { * * @param increment Whether to increment or decrement the value. */ - private void postChangeCurrentByOneFromLongPress(boolean increment) { - mInputText.clearFocus(); - removeAllCallbacks(); + private void postChangeCurrentByOneFromLongPress(boolean increment, long delayMillis) { if (mChangeCurrentByOneFromLongPressCommand == null) { mChangeCurrentByOneFromLongPressCommand = new ChangeCurrentByOneFromLongPressCommand(); + } else { + removeCallbacks(mChangeCurrentByOneFromLongPressCommand); + } + mChangeCurrentByOneFromLongPressCommand.setStep(increment); + postDelayed(mChangeCurrentByOneFromLongPressCommand, delayMillis); + } + + /** + * Removes the command for changing the current value by one. + */ + private void removeChangeCurrentByOneFromLongPress() { + if (mChangeCurrentByOneFromLongPressCommand != null) { + removeCallbacks(mChangeCurrentByOneFromLongPressCommand); + } + } + + /** + * Posts a command for beginning an edit of the current value via IME on + * long press. + */ + private void postBeginSoftInputOnLongPressCommand() { + if (mBeginSoftInputOnLongPressCommand == null) { + mBeginSoftInputOnLongPressCommand = new BeginSoftInputOnLongPressCommand(); + } else { + removeCallbacks(mBeginSoftInputOnLongPressCommand); + } + postDelayed(mBeginSoftInputOnLongPressCommand, ViewConfiguration.getLongPressTimeout()); + } + + /** + * Removes the command for beginning an edit of the current value via IME. + */ + private void removeBeginSoftInputCommand() { + if (mBeginSoftInputOnLongPressCommand != null) { + removeCallbacks(mBeginSoftInputOnLongPressCommand); } - mChangeCurrentByOneFromLongPressCommand.setIncrement(increment); - post(mChangeCurrentByOneFromLongPressCommand); } /** @@ -1845,12 +1761,12 @@ public class NumberPicker extends LinearLayout { if (mChangeCurrentByOneFromLongPressCommand != null) { removeCallbacks(mChangeCurrentByOneFromLongPressCommand); } - if (mAdjustScrollerCommand != null) { - removeCallbacks(mAdjustScrollerCommand); - } if (mSetSelectionCommand != null) { removeCallbacks(mSetSelectionCommand); } + if (mBeginSoftInputOnLongPressCommand != null) { + removeCallbacks(mBeginSoftInputOnLongPressCommand); + } } /** @@ -1888,8 +1804,7 @@ public class NumberPicker extends LinearLayout { /** * Posts an {@link SetSelectionCommand} from the given <code>selectionStart - * </code> to - * <code>selectionEnd</code>. + * </code> to <code>selectionEnd</code>. */ private void postSetSelectionCommand(int selectionStart, int selectionEnd) { if (mSetSelectionCommand == null) { @@ -1903,20 +1818,6 @@ public class NumberPicker extends LinearLayout { } /** - * Posts an {@link AdjustScrollerCommand} within the given <code> - * delayMillis</code> - * . - */ - private void postAdjustScrollerCommand(int delayMillis) { - if (mAdjustScrollerCommand == null) { - mAdjustScrollerCommand = new AdjustScrollerCommand(); - } else { - removeCallbacks(mAdjustScrollerCommand); - } - postDelayed(mAdjustScrollerCommand, delayMillis); - } - - /** * Filter for accepting only valid indices or prefixes of the string * representation of valid indices. */ @@ -1934,8 +1835,8 @@ public class NumberPicker extends LinearLayout { } @Override - public CharSequence filter(CharSequence source, int start, int end, Spanned dest, - int dstart, int dend) { + public CharSequence filter( + CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { if (mDisplayedValues == null) { CharSequence filtered = super.filter(source, start, end, dest, dstart, dend); if (filtered == null) { @@ -1981,6 +1882,27 @@ public class NumberPicker extends LinearLayout { } /** + * Ensures that the scroll wheel is adjusted i.e. there is no offset and the + * middle element is in the middle of the widget. + * + * @return Whether an adjustment has been made. + */ + private boolean ensureScrollWheelAdjusted() { + // adjust to the closest value + int deltaY = mInitialScrollOffset - mCurrentScrollOffset; + if (deltaY != 0) { + mPreviousScrollerY = 0; + if (Math.abs(deltaY) > mSelectorElementHeight / 2) { + deltaY += (deltaY > 0) ? -mSelectorElementHeight : mSelectorElementHeight; + } + mAdjustScroller.startScroll(0, 0, 0, deltaY, SELECTOR_ADJUSTMENT_DURATION_MILLIS); + invalidate(); + return true; + } + return false; + } + + /** * Command for setting the input text selection. */ class SetSelectionCommand implements Runnable { @@ -1994,39 +1916,18 @@ public class NumberPicker extends LinearLayout { } /** - * Command for adjusting the scroller to show in its center the closest of - * the displayed items. - */ - class AdjustScrollerCommand implements Runnable { - public void run() { - mPreviousScrollerY = 0; - if (mInitialScrollOffset == mCurrentScrollOffset) { - updateInputTextView(); - showInputControls(mShowInputControlsAnimimationDuration); - return; - } - // adjust to the closest value - int deltaY = mInitialScrollOffset - mCurrentScrollOffset; - if (Math.abs(deltaY) > mSelectorElementHeight / 2) { - deltaY += (deltaY > 0) ? -mSelectorElementHeight : mSelectorElementHeight; - } - mAdjustScroller.startScroll(0, 0, 0, deltaY, SELECTOR_ADJUSTMENT_DURATION_MILLIS); - invalidate(); - } - } - - /** * Command for changing the current value from a long press by one. */ class ChangeCurrentByOneFromLongPressCommand implements Runnable { private boolean mIncrement; - private void setIncrement(boolean increment) { + private void setStep(boolean increment) { mIncrement = increment; } + @Override public void run() { - changeCurrentByOne(mIncrement); + changeValueByOne(mIncrement); postDelayed(this, mLongPressUpdateInterval); } } @@ -2048,4 +1949,248 @@ public class NumberPicker extends LinearLayout { } } } + + /** + * Command for beginning soft input on long press. + */ + class BeginSoftInputOnLongPressCommand implements Runnable { + + @Override + public void run() { + showSoftInput(); + mIngonreMoveEvents = true; + } + } + + class AccessibilityNodeProviderImpl extends AccessibilityNodeProvider { + private static final int VIRTUAL_VIEW_ID_INCREMENT = 1; + + private static final int VIRTUAL_VIEW_ID_INPUT = 2; + + private static final int VIRTUAL_VIEW_ID_DECREMENT = 3; + + private final Rect mTempRect = new Rect(); + + private final int[] mTempArray = new int[2]; + + @Override + public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) { + switch (virtualViewId) { + case View.NO_ID: + return createAccessibilityNodeInfoForNumberPicker( mScrollX, mScrollY, + mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop)); + case VIRTUAL_VIEW_ID_DECREMENT: + return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_DECREMENT, + getVirtualDecrementButtonText(), mScrollX, mScrollY, + mScrollX + (mRight - mLeft), + mTopSelectionDividerTop + mSelectionDividerHeight); + case VIRTUAL_VIEW_ID_INPUT: + return createAccessibiltyNodeInfoForInputText(); + case VIRTUAL_VIEW_ID_INCREMENT: + return createAccessibilityNodeInfoForVirtualButton(VIRTUAL_VIEW_ID_INCREMENT, + getVirtualIncrementButtonText(), mScrollX, + mBottomSelectionDividerBottom - mSelectionDividerHeight, + mScrollX + (mRight - mLeft), mScrollY + (mBottom - mTop)); + } + return super.createAccessibilityNodeInfo(virtualViewId); + } + + @Override + public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String searched, + int virtualViewId) { + if (TextUtils.isEmpty(searched)) { + return Collections.emptyList(); + } + String searchedLowerCase = searched.toLowerCase(); + List<AccessibilityNodeInfo> result = new ArrayList<AccessibilityNodeInfo>(); + switch (virtualViewId) { + case View.NO_ID: { + findAccessibilityNodeInfosByTextInChild(searchedLowerCase, + VIRTUAL_VIEW_ID_DECREMENT, result); + findAccessibilityNodeInfosByTextInChild(searchedLowerCase, + VIRTUAL_VIEW_ID_INPUT, result); + findAccessibilityNodeInfosByTextInChild(searchedLowerCase, + VIRTUAL_VIEW_ID_INCREMENT, result); + return result; + } + case VIRTUAL_VIEW_ID_DECREMENT: + case VIRTUAL_VIEW_ID_INCREMENT: + case VIRTUAL_VIEW_ID_INPUT: { + findAccessibilityNodeInfosByTextInChild(searchedLowerCase, virtualViewId, + result); + return result; + } + } + return super.findAccessibilityNodeInfosByText(searched, virtualViewId); + } + + @Override + public boolean performAccessibilityAction(int action, int virtualViewId) { + switch (virtualViewId) { + case VIRTUAL_VIEW_ID_INPUT: { + switch (action) { + case AccessibilityNodeInfo.ACTION_FOCUS: { + if (!mInputText.isFocused()) { + return mInputText.requestFocus(); + } + } break; + case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: { + if (mInputText.isFocused()) { + mInputText.clearFocus(); + return true; + } + } break; + } + } break; + } + return super.performAccessibilityAction(action, virtualViewId); + } + + public void sendAccessibilityEventForVirtualView(int virtualViewId, int eventType) { + switch (virtualViewId) { + case VIRTUAL_VIEW_ID_DECREMENT: { + sendAccessibilityEventForVirtualButton(virtualViewId, eventType, + getVirtualDecrementButtonText()); + } break; + case VIRTUAL_VIEW_ID_INPUT: { + sendAccessibilityEventForVirtualText(eventType); + } break; + case VIRTUAL_VIEW_ID_INCREMENT: { + sendAccessibilityEventForVirtualButton(virtualViewId, eventType, + getVirtualIncrementButtonText()); + } break; + } + } + + private void sendAccessibilityEventForVirtualText(int eventType) { + AccessibilityEvent event = AccessibilityEvent.obtain(eventType); + mInputText.onInitializeAccessibilityEvent(event); + mInputText.onPopulateAccessibilityEvent(event); + event.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT); + requestSendAccessibilityEvent(NumberPicker.this, event); + } + + private void sendAccessibilityEventForVirtualButton(int virtualViewId, int eventType, + String text) { + AccessibilityEvent event = AccessibilityEvent.obtain(eventType); + event.setClassName(Button.class.getName()); + event.setPackageName(mContext.getPackageName()); + event.getText().add(text); + event.setEnabled(NumberPicker.this.isEnabled()); + event.setSource(NumberPicker.this, virtualViewId); + requestSendAccessibilityEvent(NumberPicker.this, event); + } + + private void findAccessibilityNodeInfosByTextInChild(String searchedLowerCase, + int virtualViewId, List<AccessibilityNodeInfo> outResult) { + switch (virtualViewId) { + case VIRTUAL_VIEW_ID_DECREMENT: { + String text = getVirtualDecrementButtonText(); + if (!TextUtils.isEmpty(text) + && text.toString().toLowerCase().contains(searchedLowerCase)) { + outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_DECREMENT)); + } + } return; + case VIRTUAL_VIEW_ID_INPUT: { + CharSequence text = mInputText.getText(); + if (!TextUtils.isEmpty(text) && + text.toString().toLowerCase().contains(searchedLowerCase)) { + outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT)); + return; + } + CharSequence contentDesc = mInputText.getText(); + if (!TextUtils.isEmpty(contentDesc) && + contentDesc.toString().toLowerCase().contains(searchedLowerCase)) { + outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INPUT)); + return; + } + } break; + case VIRTUAL_VIEW_ID_INCREMENT: { + String text = getVirtualIncrementButtonText(); + if (!TextUtils.isEmpty(text) + && text.toString().toLowerCase().contains(searchedLowerCase)) { + outResult.add(createAccessibilityNodeInfo(VIRTUAL_VIEW_ID_INCREMENT)); + } + } return; + } + } + + private AccessibilityNodeInfo createAccessibiltyNodeInfoForInputText() { + AccessibilityNodeInfo info = mInputText.createAccessibilityNodeInfo(); + info.setLongClickable(true); + info.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT); + return info; + } + + private AccessibilityNodeInfo createAccessibilityNodeInfoForVirtualButton(int virtualViewId, + String text, int left, int top, int right, int bottom) { + AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); + info.setClassName(Button.class.getName()); + info.setPackageName(mContext.getPackageName()); + info.setSource(NumberPicker.this, virtualViewId); + info.setParent(NumberPicker.this); + info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_DECREMENT); + info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT); + info.addChild(NumberPicker.this, VIRTUAL_VIEW_ID_INCREMENT); + info.setText(text); + info.setClickable(true); + info.setLongClickable(true); + info.setEnabled(NumberPicker.this.isEnabled()); + Rect boundsInParent = mTempRect; + boundsInParent.set(left, top, right, bottom); + info.setBoundsInParent(boundsInParent); + Rect boundsInScreen = boundsInParent; + int[] locationOnScreen = mTempArray; + getLocationOnScreen(locationOnScreen); + boundsInScreen.offsetTo(0, 0); + boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]); + info.setBoundsInScreen(boundsInScreen); + return info; + } + + private AccessibilityNodeInfo createAccessibilityNodeInfoForNumberPicker(int left, int top, + int right, int bottom) { + AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); + info.setClassName(Button.class.getName()); + info.setPackageName(mContext.getPackageName()); + info.setSource(NumberPicker.this); + info.setParent((View) getParent()); + info.setEnabled(NumberPicker.this.isEnabled()); + info.setScrollable(true); + Rect boundsInParent = mTempRect; + boundsInParent.set(left, top, right, bottom); + info.setBoundsInParent(boundsInParent); + Rect boundsInScreen = boundsInParent; + int[] locationOnScreen = mTempArray; + getLocationOnScreen(locationOnScreen); + boundsInScreen.offsetTo(0, 0); + boundsInScreen.offset(locationOnScreen[0], locationOnScreen[1]); + info.setBoundsInScreen(boundsInScreen); + return info; + } + + private String getVirtualDecrementButtonText() { + int value = mValue - 1; + if (mWrapSelectorWheel) { + value = getWrappedSelectorIndex(value); + } + if (value >= mMinValue) { + return (mDisplayedValues == null) ? formatNumber(value) + : mDisplayedValues[value - mMinValue]; + } + return null; + } + + private String getVirtualIncrementButtonText() { + int value = mValue + 1; + if (mWrapSelectorWheel) { + value = getWrappedSelectorIndex(value); + } + if (value <= mMaxValue) { + return (mDisplayedValues == null) ? formatNumber(value) + : mDisplayedValues[value - mMinValue]; + } + return null; + } + } } diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java index 7f53ffd..f217c9c 100644 --- a/core/java/android/widget/RadioGroup.java +++ b/core/java/android/widget/RadioGroup.java @@ -190,6 +190,8 @@ public class RadioGroup extends LinearLayout { * * @see #check(int) * @see #clearCheck() + * + * @attr ref android.R.styleable#RadioGroup_checkedButton */ public int getCheckedRadioButtonId() { return mCheckedId; diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java index e69577b..524d272 100644 --- a/core/java/android/widget/RatingBar.java +++ b/core/java/android/widget/RatingBar.java @@ -145,6 +145,8 @@ public class RatingBar extends AbsSeekBar { * by the user). * * @param isIndicator Whether it should be an indicator. + * + * @attr ref android.R.styleable#RatingBar_isIndicator */ public void setIsIndicator(boolean isIndicator) { mIsUserSeekable = !isIndicator; @@ -153,6 +155,8 @@ public class RatingBar extends AbsSeekBar { /** * @return Whether this rating bar is only an indicator. + * + * @attr ref android.R.styleable#RatingBar_isIndicator */ public boolean isIndicator() { return !mIsUserSeekable; diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 55acb74..2f72e4a 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -965,6 +965,58 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Helper action to set compound drawables on a TextView. Supports relative + * (s/t/e/b) or cardinal (l/t/r/b) arrangement. + */ + private class TextViewDrawableAction extends Action { + public TextViewDrawableAction(int viewId, boolean isRelative, int d1, int d2, int d3, int d4) { + this.viewId = viewId; + this.isRelative = isRelative; + this.d1 = d1; + this.d2 = d2; + this.d3 = d3; + this.d4 = d4; + } + + public TextViewDrawableAction(Parcel parcel) { + viewId = parcel.readInt(); + isRelative = (parcel.readInt() != 0); + d1 = parcel.readInt(); + d2 = parcel.readInt(); + d3 = parcel.readInt(); + d4 = parcel.readInt(); + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(TAG); + dest.writeInt(viewId); + dest.writeInt(isRelative ? 1 : 0); + dest.writeInt(d1); + dest.writeInt(d2); + dest.writeInt(d3); + dest.writeInt(d4); + } + + @Override + public void apply(View root, ViewGroup rootParent) { + final Context context = root.getContext(); + final TextView target = (TextView) root.findViewById(viewId); + if (target == null) return; + if (isRelative) { + target.setCompoundDrawablesRelativeWithIntrinsicBounds(d1, d2, d3, d4); + } else { + target.setCompoundDrawablesWithIntrinsicBounds(d1, d2, d3, d4); + } + } + + int viewId; + boolean isRelative = false; + int d1, d2, d3, d4; + + public final static int TAG = 11; + } + + /** * Simple class used to keep track of memory usage in a RemoteViews. * */ @@ -1043,6 +1095,9 @@ public class RemoteViews implements Parcelable, Filter { case SetRemoteViewsAdapterIntent.TAG: mActions.add(new SetRemoteViewsAdapterIntent(parcel)); break; + case TextViewDrawableAction.TAG: + mActions.add(new TextViewDrawableAction(parcel)); + break; default: throw new ActionException("Tag " + tag + " not found"); } @@ -1195,6 +1250,35 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Equivalent to calling + * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}. + * + * @param viewId The id of the view whose text should change + * @param left The id of a drawable to place to the left of the text, or 0 + * @param top The id of a drawable to place above the text, or 0 + * @param right The id of a drawable to place to the right of the text, or 0 + * @param bottom The id of a drawable to place below the text, or 0 + */ + public void setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom) { + addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom)); + } + + /** + * Equivalent to calling {@link + * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, int)}. + * + * @param viewId The id of the view whose text should change + * @param start The id of a drawable to place before the text (relative to the + * layout direction), or 0 + * @param top The id of a drawable to place above the text, or 0 + * @param end The id of a drawable to place after the text, or 0 + * @param bottom The id of a drawable to place below the text, or 0 + */ + public void setTextViewCompoundDrawablesRelative(int viewId, int start, int top, int end, int bottom) { + addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom)); + } + + /** * Equivalent to calling ImageView.setImageResource * * @param viewId The id of the view whose drawable should change diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 25dd438..e0e3e93 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -642,8 +642,7 @@ public class ScrollView extends FrameLayout { break; case MotionEvent.ACTION_POINTER_DOWN: { final int index = ev.getActionIndex(); - final float y = ev.getY(index); - mLastMotionY = y; + mLastMotionY = ev.getY(index); mActivePointerId = ev.getPointerId(index); break; } @@ -715,6 +714,7 @@ public class ScrollView extends FrameLayout { } else { super.scrollTo(scrollX, scrollY); } + awakenScrollBars(); } @@ -749,42 +749,6 @@ public class ScrollView extends FrameLayout { /** * <p> - * Finds the next focusable component that fits in this View's bounds - * (excluding fading edges) pretending that this View's top is located at - * the parameter top. - * </p> - * - * @param topFocus look for a candidate at the top of the bounds if topFocus is true, - * or at the bottom of the bounds if topFocus is false - * @param top the top offset of the bounds in which a focusable must be - * found (the fading edge is assumed to start at this position) - * @param preferredFocusable the View that has highest priority and will be - * returned if it is within my bounds (null is valid) - * @return the next focusable component in the bounds or null if none can be found - */ - private View findFocusableViewInMyBounds(final boolean topFocus, - final int top, View preferredFocusable) { - /* - * The fading edge's transparent side should be considered for focus - * since it's mostly visible, so we divide the actual fading edge length - * by 2. - */ - final int fadingEdgeLength = getVerticalFadingEdgeLength() / 2; - final int topWithoutFadingEdge = top + fadingEdgeLength; - final int bottomWithoutFadingEdge = top + getHeight() - fadingEdgeLength; - - if ((preferredFocusable != null) - && (preferredFocusable.getTop() < bottomWithoutFadingEdge) - && (preferredFocusable.getBottom() > topWithoutFadingEdge)) { - return preferredFocusable; - } - - return findFocusableViewInBounds(topFocus, topWithoutFadingEdge, - bottomWithoutFadingEdge); - } - - /** - * <p> * Finds the next focusable component that fits in the specified bounds. * </p> * @@ -1208,10 +1172,10 @@ public class ScrollView extends FrameLayout { } } - awakenScrollBars(); - - // Keep on drawing until the animation has finished. - postInvalidate(); + if (!awakenScrollBars()) { + // Keep on drawing until the animation has finished. + invalidate(); + } } else { if (mFlingStrictSpan != null) { mFlingStrictSpan.finish(); @@ -1438,7 +1402,7 @@ public class ScrollView extends FrameLayout { /** * Return true if child is a descendant of parent, (or equal to the parent). */ - private boolean isViewDescendantOf(View child, View parent) { + private static boolean isViewDescendantOf(View child, View parent) { if (child == parent) { return true; } @@ -1462,8 +1426,6 @@ public class ScrollView extends FrameLayout { mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0, Math.max(0, bottom - height), 0, height/2); - final boolean movingDown = velocityY > 0; - if (mFlingStrictSpan == null) { mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling"); } @@ -1554,7 +1516,7 @@ public class ScrollView extends FrameLayout { } } - private int clamp(int n, int my, int child) { + private static int clamp(int n, int my, int child) { if (my >= child || n < 0) { /* my >= child is this case: * |--------------- me ---------------| diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 9941c95..1f2410b 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -1798,6 +1798,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_drawableRight * @attr ref android.R.styleable#TextView_drawableBottom */ + @android.view.RemotableViewMethod public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) { final Resources resources = getContext().getResources(); setCompoundDrawablesWithIntrinsicBounds(left != 0 ? resources.getDrawable(left) : null, @@ -1967,6 +1968,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @attr ref android.R.styleable#TextView_drawableEnd * @attr ref android.R.styleable#TextView_drawableBottom */ + @android.view.RemotableViewMethod public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end, int bottom) { resetResolvedDrawables(); @@ -2042,6 +2044,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * * @attr ref android.R.styleable#TextView_drawablePadding */ + @android.view.RemotableViewMethod public void setCompoundDrawablePadding(int pad) { Drawables dr = mDrawables; if (pad == 0) { @@ -7493,7 +7496,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Returns true, only while processing a touch gesture, if the initial * touch down event caused focus to move to the text view and as a result * its selection changed. Only valid while processing the touch gesture - * of interest. + * of interest, in an editable text view. */ public boolean didTouchFocusSelect() { return mEditor != null && getEditor().mTouchFocusSelected; @@ -11755,7 +11758,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener hardwareCanvas.onPostDraw(); blockDisplayList.end(); if (USE_DISPLAY_LIST_PROPERTIES) { - blockDisplayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); + blockDisplayList.setLeftTopRightBottom(0, 0, width, height); } } } diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java index 7eff1aa..bc88b62 100644 --- a/core/java/android/widget/TimePicker.java +++ b/core/java/android/widget/TimePicker.java @@ -532,21 +532,28 @@ public class TimePicker extends FrameLayout { private void setContentDescriptions() { // Minute - String text = mContext.getString(R.string.time_picker_increment_minute_button); - mMinuteSpinner.findViewById(R.id.increment).setContentDescription(text); - text = mContext.getString(R.string.time_picker_decrement_minute_button); - mMinuteSpinner.findViewById(R.id.decrement).setContentDescription(text); + trySetContentDescription(mMinuteSpinner, R.id.increment, + R.string.time_picker_increment_minute_button); + trySetContentDescription(mMinuteSpinner, R.id.decrement, + R.string.time_picker_decrement_minute_button); // Hour - text = mContext.getString(R.string.time_picker_increment_hour_button); - mHourSpinner.findViewById(R.id.increment).setContentDescription(text); - text = mContext.getString(R.string.time_picker_decrement_hour_button); - mHourSpinner.findViewById(R.id.decrement).setContentDescription(text); + trySetContentDescription(mHourSpinner, R.id.increment, + R.string.time_picker_increment_hour_button); + trySetContentDescription(mHourSpinner, R.id.decrement, + R.string.time_picker_decrement_hour_button); // AM/PM if (mAmPmSpinner != null) { - text = mContext.getString(R.string.time_picker_increment_set_pm_button); - mAmPmSpinner.findViewById(R.id.increment).setContentDescription(text); - text = mContext.getString(R.string.time_picker_decrement_set_am_button); - mAmPmSpinner.findViewById(R.id.decrement).setContentDescription(text); + trySetContentDescription(mAmPmSpinner, R.id.increment, + R.string.time_picker_increment_set_pm_button); + trySetContentDescription(mAmPmSpinner, R.id.decrement, + R.string.time_picker_decrement_set_am_button); + } + } + + private void trySetContentDescription(View root, int viewId, int contDescResId) { + View target = root.findViewById(viewId); + if (target != null) { + target.setContentDescription(mContext.getString(contDescResId)); } } |
