diff options
Diffstat (limited to 'core/java')
53 files changed, 1393 insertions, 803 deletions
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java index e269c31..ad8c971 100755 --- a/core/java/android/animation/ValueAnimator.java +++ b/core/java/android/animation/ValueAnimator.java @@ -83,15 +83,61 @@ public class ValueAnimator extends Animator { */ private long mSeekTime = -1; + // TODO: We access the following ThreadLocal variables often, some of them on every update. + // If ThreadLocal access is significantly expensive, we may want to put all of these + // fields into a structure sot hat we just access ThreadLocal once to get the reference + // to that structure, then access the structure directly for each field. + // The static sAnimationHandler processes the internal timing loop on which all animations // are based - private static AnimationHandler sAnimationHandler; + private static ThreadLocal<AnimationHandler> sAnimationHandler = + new ThreadLocal<AnimationHandler>(); + + // The per-thread list of all active animations + private static final ThreadLocal<ArrayList<ValueAnimator>> sAnimations = + new ThreadLocal<ArrayList<ValueAnimator>>() { + @Override + protected ArrayList<ValueAnimator> initialValue() { + return new ArrayList<ValueAnimator>(); + } + }; + + // The per-thread set of animations to be started on the next animation frame + private static final ThreadLocal<ArrayList<ValueAnimator>> sPendingAnimations = + new ThreadLocal<ArrayList<ValueAnimator>>() { + @Override + protected ArrayList<ValueAnimator> initialValue() { + return new ArrayList<ValueAnimator>(); + } + }; + + /** + * Internal per-thread collections used to avoid set collisions as animations start and end + * while being processed. + */ + private static final ThreadLocal<ArrayList<ValueAnimator>> sDelayedAnims = + new ThreadLocal<ArrayList<ValueAnimator>>() { + @Override + protected ArrayList<ValueAnimator> initialValue() { + return new ArrayList<ValueAnimator>(); + } + }; - // The static list of all active animations - private static final ArrayList<ValueAnimator> sAnimations = new ArrayList<ValueAnimator>(); + private static final ThreadLocal<ArrayList<ValueAnimator>> sEndingAnims = + new ThreadLocal<ArrayList<ValueAnimator>>() { + @Override + protected ArrayList<ValueAnimator> initialValue() { + return new ArrayList<ValueAnimator>(); + } + }; - // The set of animations to be started on the next animation frame - private static final ArrayList<ValueAnimator> sPendingAnimations = new ArrayList<ValueAnimator>(); + private static final ThreadLocal<ArrayList<ValueAnimator>> sReadyAnims = + new ThreadLocal<ArrayList<ValueAnimator>>() { + @Override + protected ArrayList<ValueAnimator> initialValue() { + return new ArrayList<ValueAnimator>(); + } + }; // The time interpolator to be used if none is set on the animation private static final TimeInterpolator sDefaultInterpolator = @@ -136,14 +182,6 @@ public class ValueAnimator extends Animator { private int mPlayingState = STOPPED; /** - * Internal collections used to avoid set collisions as animations start and end while being - * processed. - */ - private static final ArrayList<ValueAnimator> sEndingAnims = new ArrayList<ValueAnimator>(); - private static final ArrayList<ValueAnimator> sDelayedAnims = new ArrayList<ValueAnimator>(); - private static final ArrayList<ValueAnimator> sReadyAnims = new ArrayList<ValueAnimator>(); - - /** * Flag that denotes whether the animation is set up and ready to go. Used to * set up animation that has not yet been started. */ @@ -609,11 +647,14 @@ public class ValueAnimator extends Animator { @Override public void handleMessage(Message msg) { boolean callAgain = true; + ArrayList<ValueAnimator> animations = sAnimations.get(); + ArrayList<ValueAnimator> delayedAnims = sDelayedAnims.get(); switch (msg.what) { // TODO: should we avoid sending frame message when starting if we // were already running? case ANIMATION_START: - if (sAnimations.size() > 0 || sDelayedAnims.size() > 0) { + ArrayList<ValueAnimator> pendingAnimations = sPendingAnimations.get(); + if (animations.size() > 0 || delayedAnims.size() > 0) { callAgain = false; } // pendingAnims holds any animations that have requested to be started @@ -621,10 +662,10 @@ public class ValueAnimator extends Animator { // cause more to be added to the pending list (for example, if one animation // starting triggers another starting). So we loop until sPendingAnimations // is empty. - while (sPendingAnimations.size() > 0) { + while (pendingAnimations.size() > 0) { ArrayList<ValueAnimator> pendingCopy = - (ArrayList<ValueAnimator>) sPendingAnimations.clone(); - sPendingAnimations.clear(); + (ArrayList<ValueAnimator>) pendingAnimations.clone(); + pendingAnimations.clear(); int count = pendingCopy.size(); for (int i = 0; i < count; ++i) { ValueAnimator anim = pendingCopy.get(i); @@ -633,7 +674,7 @@ public class ValueAnimator extends Animator { anim.mPlayingState == CANCELED) { anim.startAnimation(); } else { - sDelayedAnims.add(anim); + delayedAnims.add(anim); } } } @@ -642,45 +683,47 @@ public class ValueAnimator extends Animator { // currentTime holds the common time for all animations processed // during this frame long currentTime = AnimationUtils.currentAnimationTimeMillis(); + ArrayList<ValueAnimator> readyAnims = sReadyAnims.get(); + ArrayList<ValueAnimator> endingAnims = sEndingAnims.get(); // First, process animations currently sitting on the delayed queue, adding // them to the active animations if they are ready - int numDelayedAnims = sDelayedAnims.size(); + int numDelayedAnims = delayedAnims.size(); for (int i = 0; i < numDelayedAnims; ++i) { - ValueAnimator anim = sDelayedAnims.get(i); + ValueAnimator anim = delayedAnims.get(i); if (anim.delayedAnimationFrame(currentTime)) { - sReadyAnims.add(anim); + readyAnims.add(anim); } } - int numReadyAnims = sReadyAnims.size(); + int numReadyAnims = readyAnims.size(); if (numReadyAnims > 0) { for (int i = 0; i < numReadyAnims; ++i) { - ValueAnimator anim = sReadyAnims.get(i); + ValueAnimator anim = readyAnims.get(i); anim.startAnimation(); - sDelayedAnims.remove(anim); + delayedAnims.remove(anim); } - sReadyAnims.clear(); + readyAnims.clear(); } // Now process all active animations. The return value from animationFrame() // tells the handler whether it should now be ended - int numAnims = sAnimations.size(); + int numAnims = animations.size(); for (int i = 0; i < numAnims; ++i) { - ValueAnimator anim = sAnimations.get(i); + ValueAnimator anim = animations.get(i); if (anim.animationFrame(currentTime)) { - sEndingAnims.add(anim); + endingAnims.add(anim); } } - if (sEndingAnims.size() > 0) { - for (int i = 0; i < sEndingAnims.size(); ++i) { - sEndingAnims.get(i).endAnimation(); + if (endingAnims.size() > 0) { + for (int i = 0; i < endingAnims.size(); ++i) { + endingAnims.get(i).endAnimation(); } - sEndingAnims.clear(); + endingAnims.clear(); } // If there are still active or delayed animations, call the handler again // after the frameDelay - if (callAgain && (!sAnimations.isEmpty() || !sDelayedAnims.isEmpty())) { + if (callAgain && (!animations.isEmpty() || !delayedAnims.isEmpty())) { sendEmptyMessageDelayed(ANIMATION_FRAME, Math.max(0, sFrameDelay - (AnimationUtils.currentAnimationTimeMillis() - currentTime))); } @@ -935,13 +978,13 @@ public class ValueAnimator extends Animator { mCurrentIteration = 0; mPlayingState = STOPPED; mStartedDelay = false; - sPendingAnimations.add(this); - if (sAnimationHandler == null) { - sAnimationHandler = new AnimationHandler(); + sPendingAnimations.get().add(this); + AnimationHandler animationHandler = sAnimationHandler.get(); + if (animationHandler == null) { + animationHandler = new AnimationHandler(); + sAnimationHandler.set(animationHandler); } - // TODO: does this put too many messages on the queue if the handler - // is already running? - sAnimationHandler.sendEmptyMessage(ANIMATION_START); + animationHandler.sendEmptyMessage(ANIMATION_START); } @Override @@ -965,14 +1008,16 @@ public class ValueAnimator extends Animator { @Override public void end() { - if (!sAnimations.contains(this) && !sPendingAnimations.contains(this)) { + if (!sAnimations.get().contains(this) && !sPendingAnimations.get().contains(this)) { // Special case if the animation has not yet started; get it ready for ending mStartedDelay = false; - sPendingAnimations.add(this); - if (sAnimationHandler == null) { - sAnimationHandler = new AnimationHandler(); + sPendingAnimations.get().add(this); + AnimationHandler animationHandler = sAnimationHandler.get(); + if (animationHandler == null) { + animationHandler = new AnimationHandler(); + sAnimationHandler.set(animationHandler); } - sAnimationHandler.sendEmptyMessage(ANIMATION_START); + animationHandler.sendEmptyMessage(ANIMATION_START); } // Just set the ENDED flag - this causes the animation to end the next time a frame // is processed. @@ -1009,7 +1054,7 @@ public class ValueAnimator extends Animator { * called on the UI thread. */ private void endAnimation() { - sAnimations.remove(this); + sAnimations.get().remove(this); mPlayingState = STOPPED; if (mListeners != null) { ArrayList<AnimatorListener> tmpListeners = @@ -1026,7 +1071,7 @@ public class ValueAnimator extends Animator { */ private void startAnimation() { initAnimation(); - sAnimations.add(this); + sAnimations.get().add(this); if (mStartDelay > 0 && mListeners != null) { // Listeners were already notified in start() if startDelay is 0; this is // just for delayed animations @@ -1225,6 +1270,6 @@ public class ValueAnimator extends Animator { * @hide */ public static int getCurrentAnimationsCount() { - return sAnimations.size(); + return sAnimations.get().size(); } } diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index f08d88d..33f88d8 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -645,11 +645,13 @@ public class Activity extends ContextThemeWrapper Activity mParent; boolean mCalled; boolean mCheckedForLoaderManager; - boolean mStarted; + boolean mLoadersStarted; private boolean mResumed; private boolean mStopped; boolean mFinished; boolean mStartedActivity; + /** true if the activity is going through a transient pause */ + /*package*/ boolean mTemporaryPause = false; /** true if the activity is being destroyed in order to recreate it with a new configuration */ /*package*/ boolean mChangingConfigurations = false; /*package*/ int mConfigChangeFlags; @@ -768,7 +770,7 @@ public class Activity extends ContextThemeWrapper return mLoaderManager; } mCheckedForLoaderManager = true; - mLoaderManager = getLoaderManager(-1, mStarted, true); + mLoaderManager = getLoaderManager(-1, mLoadersStarted, true); return mLoaderManager; } @@ -777,9 +779,13 @@ public class Activity extends ContextThemeWrapper mAllLoaderManagers = new SparseArray<LoaderManagerImpl>(); } LoaderManagerImpl lm = mAllLoaderManagers.get(index); - if (lm == null && create) { - lm = new LoaderManagerImpl(started); - mAllLoaderManagers.put(index, lm); + if (lm == null) { + if (create) { + lm = new LoaderManagerImpl(this, started); + mAllLoaderManagers.put(index, lm); + } + } else { + lm.updateActivity(this); } return lm; } @@ -979,13 +985,16 @@ public class Activity extends ContextThemeWrapper */ protected void onStart() { mCalled = true; - mStarted = true; - if (mLoaderManager != null) { - mLoaderManager.doStart(); - } else if (!mCheckedForLoaderManager) { - mLoaderManager = getLoaderManager(-1, mStarted, false); + + if (!mLoadersStarted) { + mLoadersStarted = true; + if (mLoaderManager != null) { + mLoaderManager.doStart(); + } else if (!mCheckedForLoaderManager) { + mLoaderManager = getLoaderManager(-1, mLoadersStarted, false); + } + mCheckedForLoaderManager = true; } - mCheckedForLoaderManager = true; } /** @@ -1219,7 +1228,6 @@ public class Activity extends ContextThemeWrapper */ protected void onPause() { mCalled = true; - QueuedWork.waitToFinish(); } /** @@ -4249,7 +4257,7 @@ public class Activity extends ContextThemeWrapper } final void performStart() { - mFragments.mStateSaved = false; + mFragments.noteStateNotSaved(); mCalled = false; mFragments.execPendingActions(); mInstrumentation.callActivityOnStart(this); @@ -4267,7 +4275,7 @@ public class Activity extends ContextThemeWrapper } final void performRestart() { - mFragments.mStateSaved = false; + mFragments.noteStateNotSaved(); synchronized (mManagedCursors) { final int N = mManagedCursors.size(); @@ -4347,8 +4355,8 @@ public class Activity extends ContextThemeWrapper } final void performStop() { - if (mStarted) { - mStarted = false; + if (mLoadersStarted) { + mLoadersStarted = false; if (mLoaderManager != null) { if (!mChangingConfigurations) { mLoaderManager.doStop(); @@ -4407,7 +4415,7 @@ public class Activity extends ContextThemeWrapper if (Config.LOGV) Log.v( TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode + ", resCode=" + resultCode + ", data=" + data); - mFragments.mStateSaved = false; + mFragments.noteStateNotSaved(); if (who == null) { onActivityResult(requestCode, resultCode, data); } else { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index f3f7ee7..754295a 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -117,12 +117,14 @@ final class RemoteServiceException extends AndroidRuntimeException { * {@hide} */ public final class ActivityThread { - static final String TAG = "ActivityThread"; + /** @hide */ + public static final String TAG = "ActivityThread"; private static final android.graphics.Bitmap.Config THUMBNAIL_FORMAT = Bitmap.Config.RGB_565; private static final boolean DEBUG = false; static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; static final boolean DEBUG_MESSAGES = false; - static final boolean DEBUG_BROADCAST = false; + /** @hide */ + public static final boolean DEBUG_BROADCAST = false; private static final boolean DEBUG_RESULTS = false; private static final boolean DEBUG_BACKUP = false; private static final boolean DEBUG_CONFIGURATION = false; @@ -262,18 +264,20 @@ public final class ActivityThread { } } - private static final class ReceiverData { + private static final class ReceiverData extends BroadcastReceiver.PendingResult { + public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras, + boolean ordered, boolean sticky, IBinder token) { + super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky, token); + this.intent = intent; + } + Intent intent; ActivityInfo info; - int resultCode; - String resultData; - Bundle resultExtras; - boolean sync; - boolean resultAbort; public String toString() { return "ReceiverData{intent=" + intent + " packageName=" + - info.packageName + " resultCode=" + resultCode - + " resultData=" + resultData + " resultExtras=" + resultExtras + "}"; + info.packageName + " resultCode=" + getResultCode() + + " resultData=" + getResultData() + " resultExtras=" + + getResultExtras(false) + "}"; } } @@ -466,15 +470,9 @@ public final class ActivityThread { public final void scheduleReceiver(Intent intent, ActivityInfo info, int resultCode, String data, Bundle extras, boolean sync) { - ReceiverData r = new ReceiverData(); - - r.intent = intent; + ReceiverData r = new ReceiverData(intent, resultCode, data, extras, + sync, false, mAppThread.asBinder()); r.info = info; - r.resultCode = resultCode; - r.resultData = data; - r.resultExtras = extras; - r.sync = sync; - queueOrSendMessage(H.RECEIVER, r); } @@ -1757,6 +1755,7 @@ public final class ActivityThread { for (int i=0; i<N; i++) { Intent intent = intents.get(i); intent.setExtrasClassLoader(r.activity.getClassLoader()); + r.activity.mFragments.noteStateNotSaved(); mInstrumentation.callActivityOnNewIntent(r.activity, intent); } } @@ -1767,11 +1766,13 @@ public final class ActivityThread { if (r != null) { final boolean resumed = !r.paused; if (resumed) { + r.activity.mTemporaryPause = true; mInstrumentation.callActivityOnPause(r.activity); } deliverNewIntents(r, intents); if (resumed) { mInstrumentation.callActivityOnResume(r.activity); + r.activity.mTemporaryPause = false; } } } @@ -1796,18 +1797,12 @@ public final class ActivityThread { try { java.lang.ClassLoader cl = packageInfo.getClassLoader(); data.intent.setExtrasClassLoader(cl); - if (data.resultExtras != null) { - data.resultExtras.setClassLoader(cl); - } + data.setExtrasClassLoader(cl); receiver = (BroadcastReceiver)cl.loadClass(component).newInstance(); } catch (Exception e) { - try { - if (DEBUG_BROADCAST) Slog.i(TAG, - "Finishing failed broadcast to " + data.intent.getComponent()); - mgr.finishReceiver(mAppThread.asBinder(), data.resultCode, - data.resultData, data.resultExtras, data.resultAbort); - } catch (RemoteException ex) { - } + if (DEBUG_BROADCAST) Slog.i(TAG, + "Finishing failed broadcast to " + data.intent.getComponent()); + data.sendFinished(mgr); throw new RuntimeException( "Unable to instantiate receiver " + component + ": " + e.toString(), e); @@ -1825,20 +1820,13 @@ public final class ActivityThread { + ", dir=" + packageInfo.getAppDir()); ContextImpl context = (ContextImpl)app.getBaseContext(); - receiver.setOrderedHint(true); - receiver.setResult(data.resultCode, data.resultData, - data.resultExtras); - receiver.setOrderedHint(data.sync); + receiver.setPendingResult(data); receiver.onReceive(context.getReceiverRestrictedContext(), data.intent); } catch (Exception e) { - try { - if (DEBUG_BROADCAST) Slog.i(TAG, - "Finishing failed broadcast to " + data.intent.getComponent()); - mgr.finishReceiver(mAppThread.asBinder(), data.resultCode, - data.resultData, data.resultExtras, data.resultAbort); - } catch (RemoteException ex) { - } + if (DEBUG_BROADCAST) Slog.i(TAG, + "Finishing failed broadcast to " + data.intent.getComponent()); + data.sendFinished(mgr); if (!mInstrumentation.onException(receiver, e)) { throw new RuntimeException( "Unable to start receiver " + component @@ -1846,22 +1834,8 @@ public final class ActivityThread { } } - QueuedWork.waitToFinish(); - - try { - if (data.sync) { - if (DEBUG_BROADCAST) Slog.i(TAG, - "Finishing ordered broadcast to " + data.intent.getComponent()); - mgr.finishReceiver( - mAppThread.asBinder(), receiver.getResultCode(), - receiver.getResultData(), receiver.getResultExtras(false), - receiver.getAbortBroadcast()); - } else { - if (DEBUG_BROADCAST) Slog.i(TAG, - "Finishing broadcast to " + data.intent.getComponent()); - mgr.finishReceiver(mAppThread.asBinder(), 0, null, null, false); - } - } catch (RemoteException ex) { + if (receiver.getPendingResult() != null) { + data.finish(); } } @@ -2344,6 +2318,9 @@ public final class ActivityThread { r.activity.mConfigChangeFlags |= configChanges; Bundle state = performPauseActivity(token, finished, true); + // Make sure any pending writes are now committed. + QueuedWork.waitToFinish(); + // Tell the activity manager we have paused. try { ActivityManagerNative.getDefault().activityPaused(token, state); @@ -2594,6 +2571,7 @@ public final class ActivityThread { try { // Now we are idle. r.activity.mCalled = false; + r.activity.mTemporaryPause = true; mInstrumentation.callActivityOnPause(r.activity); if (!r.activity.mCalled) { throw new SuperNotCalledException( @@ -2614,6 +2592,7 @@ public final class ActivityThread { deliverResults(r, res.results); if (resumed) { mInstrumentation.callActivityOnResume(r.activity); + r.activity.mTemporaryPause = false; } } } diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java index 5654599..570e494 100644 --- a/core/java/android/app/DownloadManager.java +++ b/core/java/android/app/DownloadManager.java @@ -26,7 +26,6 @@ import android.net.ConnectivityManager; import android.net.Uri; import android.os.Environment; import android.os.ParcelFileDescriptor; -import android.provider.BaseColumns; import android.provider.Downloads; import android.util.Pair; @@ -337,13 +336,34 @@ public class DownloadManager { private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>(); private CharSequence mTitle; private CharSequence mDescription; - private boolean mShowNotification = true; private String mMimeType; private boolean mRoamingAllowed = true; private int mAllowedNetworkTypes = ~0; // default to all network types allowed private boolean mIsVisibleInDownloadsUi = true; /** + * This download is visible but only shows in the notifications + * while it's in progress. + */ + public static final int VISIBILITY_VISIBLE = 0; + + /** + * This download is visible and shows in the notifications while + * in progress and after completion. + */ + public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1; + + /** + * This download doesn't show in the UI or in the notifications. + */ + public static final int VISIBILITY_HIDDEN = 2; + + /** can take any of the following values: {@link #VISIBILITY_HIDDEN} + * {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}, {@link #VISIBILITY_VISIBLE} + */ + private int mNotificationVisibility = VISIBILITY_VISIBLE; + + /** * @param uri the HTTP URI to download. */ public Request(Uri uri) { @@ -475,9 +495,33 @@ public class DownloadManager { * * @param show whether the download manager should show a notification for this download. * @return this object + * @deprecated use {@link #setNotificationVisibility(int)} */ + @Deprecated public Request setShowRunningNotification(boolean show) { - mShowNotification = show; + return (show) ? setNotificationVisibility(VISIBILITY_VISIBLE) : + setNotificationVisibility(VISIBILITY_HIDDEN); + } + + /** + * Control whether a system notification is posted by the download manager while this + * download is running or when it is completed. + * If enabled, the download manager posts notifications about downloads + * through the system {@link android.app.NotificationManager}. + * By default, a notification is shown only when the download is in progress. + *<p> + * It can take the following values: {@link #VISIBILITY_HIDDEN}, + * {@link #VISIBILITY_VISIBLE}, + * {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}. + *<p> + * If set to {@link #VISIBILITY_HIDDEN}, this requires the permission + * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION. + * + * @param visibility the visibility setting value + * @return this object + */ + public Request setNotificationVisibility(int visibility) { + mNotificationVisibility = visibility; return this; } @@ -540,10 +584,7 @@ public class DownloadManager { putIfNonNull(values, Downloads.Impl.COLUMN_DESCRIPTION, mDescription); putIfNonNull(values, Downloads.Impl.COLUMN_MIME_TYPE, mMimeType); - values.put(Downloads.Impl.COLUMN_VISIBILITY, - mShowNotification ? Downloads.Impl.VISIBILITY_VISIBLE - : Downloads.Impl.VISIBILITY_HIDDEN); - + values.put(Downloads.Impl.COLUMN_VISIBILITY, mNotificationVisibility); values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes); values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed); values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, mIsVisibleInDownloadsUi); diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 12bf7e5..f27a15e 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -84,6 +84,10 @@ final class FragmentState implements Parcelable { return mInstance; } + if (mArguments != null) { + mArguments.setClassLoader(activity.getClassLoader()); + } + mInstance = Fragment.instantiate(activity, mClassName, mArguments); if (mSavedFragmentState != null) { @@ -403,7 +407,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener View mView; LoaderManagerImpl mLoaderManager; - boolean mStarted; + boolean mLoadersStarted; boolean mCheckedForLoaderManager; /** @@ -728,7 +732,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener return mLoaderManager; } mCheckedForLoaderManager = true; - mLoaderManager = mActivity.getLoaderManager(mIndex, mStarted, true); + mLoaderManager = mActivity.getLoaderManager(mIndex, mLoadersStarted, true); return mLoaderManager; } @@ -880,13 +884,16 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener */ public void onStart() { mCalled = true; - mStarted = true; - if (!mCheckedForLoaderManager) { - mCheckedForLoaderManager = true; - mLoaderManager = mActivity.getLoaderManager(mIndex, mStarted, false); - } - if (mLoaderManager != null) { - mLoaderManager.doStart(); + + if (!mLoadersStarted) { + mLoadersStarted = true; + if (!mCheckedForLoaderManager) { + mCheckedForLoaderManager = true; + mLoaderManager = mActivity.getLoaderManager(mIndex, mLoadersStarted, false); + } + if (mLoaderManager != null) { + mLoaderManager.doStart(); + } } } @@ -971,7 +978,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener // + " mLoaderManager=" + mLoaderManager); if (!mCheckedForLoaderManager) { mCheckedForLoaderManager = true; - mLoaderManager = mActivity.getLoaderManager(mIndex, mStarted, false); + mLoaderManager = mActivity.getLoaderManager(mIndex, mLoadersStarted, false); } if (mLoaderManager != null) { mLoaderManager.doDestroy(); @@ -1182,7 +1189,7 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener } if (mLoaderManager != null) { writer.print(prefix); writer.print("mLoaderManager="); writer.print(mLoaderManager); - writer.print(" mStarted="); writer.print(mStarted); + writer.print(" mLoadersStarted="); writer.print(mLoadersStarted); writer.print(" mCheckedForLoaderManager="); writer.println(mCheckedForLoaderManager); } @@ -1190,11 +1197,12 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener void performStop() { onStop(); - if (mStarted) { - mStarted = false; + + if (mLoadersStarted) { + mLoadersStarted = false; if (!mCheckedForLoaderManager) { mCheckedForLoaderManager = true; - mLoaderManager = mActivity.getLoaderManager(mIndex, mStarted, false); + mLoaderManager = mActivity.getLoaderManager(mIndex, mLoadersStarted, false); } if (mLoaderManager != null) { if (mActivity == null || !mActivity.mChangingConfigurations) { diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 37e7253..45f9325 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -86,6 +86,15 @@ public interface FragmentManager { /** * Start a series of edit operations on the Fragments associated with * this FragmentManager. + * + * <p>Note: A fragment transaction can only be created/committed prior + * to an activity saving its state. If you try to commit a transaction + * after {@link Activity#onSaveInstanceState Activity.onSaveInstanceState()} + * (and prior to a following {@link Activity#onStart Activity.onStart} + * or {@link Activity#onResume Activity.onResume()}, you will get an error. + * This is because the framework takes care of saving your current fragments + * in the state, and if changes are made after the state is saved then they + * will be lost.</p> */ public FragmentTransaction openTransaction(); @@ -271,6 +280,7 @@ final class FragmentManagerImpl implements FragmentManager { boolean mNeedMenuInvalidate; boolean mStateSaved; + String mNoTransactionsBecause; // Temporary vars for state save and restore. Bundle mStateBundle = null; @@ -843,6 +853,10 @@ final class FragmentManagerImpl implements FragmentManager { throw new IllegalStateException( "Can not perform this action after onSaveInstanceState"); } + if (mNoTransactionsBecause != null) { + throw new IllegalStateException( + "Can not perform this action inside of " + mNoTransactionsBecause); + } synchronized (this) { if (mPendingActions == null) { mPendingActions = new ArrayList<Runnable>(); @@ -1186,6 +1200,7 @@ final class FragmentManagerImpl implements FragmentManager { f.mInLayout = false; f.mAdded = false; if (fs.mSavedFragmentState != null) { + fs.mSavedFragmentState.setClassLoader(mActivity.getClassLoader()); f.mSavedViewState = fs.mSavedFragmentState.getSparseParcelableArray( FragmentManagerImpl.VIEW_STATE_TAG); } @@ -1271,6 +1286,10 @@ final class FragmentManagerImpl implements FragmentManager { mActivity = activity; } + public void noteStateNotSaved() { + mStateSaved = false; + } + public void dispatchCreate() { mStateSaved = false; moveToState(Fragment.CREATED, false); diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 0644f96..7f24d27 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -660,37 +660,40 @@ final class LoadedApk { final IntentReceiverLeaked mLocation; RuntimeException mUnregisterLocation; - final class Args implements Runnable { + final class Args extends BroadcastReceiver.PendingResult implements Runnable { private Intent mCurIntent; - private int mCurCode; - private String mCurData; - private Bundle mCurMap; - private boolean mCurOrdered; - private boolean mCurSticky; - + private final boolean mOrdered; + + public Args(Intent intent, int resultCode, String resultData, Bundle resultExtras, + boolean ordered, boolean sticky) { + super(resultCode, resultData, resultExtras, + mRegistered ? TYPE_REGISTERED : TYPE_UNREGISTERED, + ordered, sticky, mIIntentReceiver.asBinder()); + mCurIntent = intent; + mOrdered = ordered; + } + public void run() { - BroadcastReceiver receiver = mReceiver; + final BroadcastReceiver receiver = mReceiver; + final boolean ordered = mOrdered; + if (ActivityThread.DEBUG_BROADCAST) { int seq = mCurIntent.getIntExtra("seq", -1); Slog.i(ActivityThread.TAG, "Dispatching broadcast " + mCurIntent.getAction() + " seq=" + seq + " to " + mReceiver); Slog.i(ActivityThread.TAG, " mRegistered=" + mRegistered - + " mCurOrdered=" + mCurOrdered); + + " mOrderedHint=" + ordered); } - IActivityManager mgr = ActivityManagerNative.getDefault(); - Intent intent = mCurIntent; + final IActivityManager mgr = ActivityManagerNative.getDefault(); + final Intent intent = mCurIntent; mCurIntent = null; if (receiver == null) { - if (mRegistered && mCurOrdered) { - try { - if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, - "Finishing null broadcast to " + mReceiver); - mgr.finishReceiver(mIIntentReceiver, - mCurCode, mCurData, mCurMap, false); - } catch (RemoteException ex) { - } + if (mRegistered && ordered) { + if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, + "Finishing null broadcast to " + mReceiver); + sendFinished(mgr); } return; } @@ -698,24 +701,14 @@ final class LoadedApk { try { ClassLoader cl = mReceiver.getClass().getClassLoader(); intent.setExtrasClassLoader(cl); - if (mCurMap != null) { - mCurMap.setClassLoader(cl); - } - receiver.setOrderedHint(true); - receiver.setResult(mCurCode, mCurData, mCurMap); - receiver.clearAbortBroadcast(); - receiver.setOrderedHint(mCurOrdered); - receiver.setInitialStickyHint(mCurSticky); + setExtrasClassLoader(cl); + receiver.setPendingResult(this); receiver.onReceive(mContext, intent); } catch (Exception e) { - if (mRegistered && mCurOrdered) { - try { - if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, - "Finishing failed broadcast to " + mReceiver); - mgr.finishReceiver(mIIntentReceiver, - mCurCode, mCurData, mCurMap, false); - } catch (RemoteException ex) { - } + if (mRegistered && ordered) { + if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, + "Finishing failed broadcast to " + mReceiver); + sendFinished(mgr); } if (mInstrumentation == null || !mInstrumentation.onException(mReceiver, e)) { @@ -724,17 +717,9 @@ final class LoadedApk { + " in " + mReceiver, e); } } - if (mRegistered && mCurOrdered) { - try { - if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, - "Finishing broadcast to " + mReceiver); - mgr.finishReceiver(mIIntentReceiver, - receiver.getResultCode(), - receiver.getResultData(), - receiver.getResultExtras(false), - receiver.getAbortBroadcast()); - } catch (RemoteException ex) { - } + + if (receiver.getPendingResult() != null) { + finish(); } } } @@ -798,23 +783,13 @@ final class LoadedApk { Slog.i(ActivityThread.TAG, "Enqueueing broadcast " + intent.getAction() + " seq=" + seq + " to " + mReceiver); } - Args args = new Args(); - args.mCurIntent = intent; - args.mCurCode = resultCode; - args.mCurData = data; - args.mCurMap = extras; - args.mCurOrdered = ordered; - args.mCurSticky = sticky; + Args args = new Args(intent, resultCode, data, extras, ordered, sticky); if (!mActivityThread.post(args)) { if (mRegistered && ordered) { IActivityManager mgr = ActivityManagerNative.getDefault(); - try { - if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, - "Finishing sync broadcast to " + mReceiver); - mgr.finishReceiver(mIIntentReceiver, args.mCurCode, - args.mCurData, args.mCurMap, false); - } catch (RemoteException ex) { - } + if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, + "Finishing sync broadcast to " + mReceiver); + args.sendFinished(mgr); } } } diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java index 28abcaa..4d4ea9a 100644 --- a/core/java/android/app/LoaderManager.java +++ b/core/java/android/app/LoaderManager.java @@ -40,7 +40,12 @@ public interface LoaderManager { public Loader<D> onCreateLoader(int id, Bundle args); /** - * Called when a previously created loader has finished its load. + * Called when a previously created loader has finished its load. Note + * that normally an application is <em>not</em> allowed to commit fragment + * transactions while in this call, since it can happen after an + * activity's state is saved. See {@link FragmentManager#openTransaction() + * FragmentManager.openTransaction()} for further discussion on this. + * * @param loader The Loader that has finished. * @param data The data generated by the Loader. */ @@ -102,6 +107,7 @@ class LoaderManagerImpl implements LoaderManager { // previously run loader until the new loader's data is available. final SparseArray<LoaderInfo> mInactiveLoaders = new SparseArray<LoaderInfo>(); + Activity mActivity; boolean mStarted; boolean mRetaining; boolean mRetainingStarted; @@ -172,12 +178,12 @@ class LoaderManagerImpl implements LoaderManager { stop(); } } - if (mStarted && mData != null && mCallbacks != null) { + if (mStarted && mData != null) { // This loader was retained, and now at the point of // finishing the retain we find we remain started, have // our data, and the owner has a new callback... so // let's deliver the data now. - mCallbacks.onLoadFinished(mLoader, mData); + callOnLoadFinished(mLoader, mData); } } } @@ -219,9 +225,7 @@ class LoaderManagerImpl implements LoaderManager { // Notify of the new data so the app can switch out the old data before // we try to destroy it. mData = data; - if (mCallbacks != null) { - mCallbacks.onLoadFinished(loader, data); - } + callOnLoadFinished(loader, data); if (DEBUG) Log.v(TAG, "onLoadFinished returned: " + this); @@ -236,6 +240,23 @@ class LoaderManagerImpl implements LoaderManager { } } + void callOnLoadFinished(Loader<Object> loader, Object data) { + if (mCallbacks != null) { + String lastBecause = null; + if (mActivity != null) { + lastBecause = mActivity.mFragments.mNoTransactionsBecause; + mActivity.mFragments.mNoTransactionsBecause = "onLoadFinished"; + } + try { + mCallbacks.onLoadFinished(loader, data); + } finally { + if (mActivity != null) { + mActivity.mFragments.mNoTransactionsBecause = lastBecause; + } + } + } + } + @Override public String toString() { StringBuilder sb = new StringBuilder(64); @@ -252,10 +273,15 @@ class LoaderManagerImpl implements LoaderManager { } } - LoaderManagerImpl(boolean started) { + LoaderManagerImpl(Activity activity, boolean started) { + mActivity = activity; mStarted = started; } + void updateActivity(Activity activity) { + mActivity = activity; + } + private LoaderInfo createLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<Object> callback) { LoaderInfo info = new LoaderInfo(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); @@ -286,7 +312,7 @@ class LoaderManagerImpl implements LoaderManager { if (info.mData != null && mStarted) { // If the loader has already generated its data, report it now. - info.mCallbacks.onLoadFinished(info.mLoader, info.mData); + info.callOnLoadFinished(info.mLoader, info.mData); } return (Loader<D>)info.mLoader; @@ -348,7 +374,13 @@ class LoaderManagerImpl implements LoaderManager { void doStart() { if (DEBUG) Log.v(TAG, "Starting: " + this); - + if (mStarted) { + RuntimeException e = new RuntimeException("here"); + e.fillInStackTrace(); + Log.w(TAG, "Called doStart when already started: " + this, e); + return; + } + // Call out to sub classes so they can start their loaders // Let the existing loaders know that we want to be notified when a load is complete for (int i = mLoaders.size()-1; i >= 0; i--) { @@ -359,6 +391,12 @@ class LoaderManagerImpl implements LoaderManager { void doStop() { if (DEBUG) Log.v(TAG, "Stopping: " + this); + if (!mStarted) { + RuntimeException e = new RuntimeException("here"); + e.fillInStackTrace(); + Log.w(TAG, "Called doStop when not started: " + this, e); + return; + } for (int i = mLoaders.size()-1; i >= 0; i--) { mLoaders.valueAt(i).stop(); @@ -368,6 +406,12 @@ class LoaderManagerImpl implements LoaderManager { void doRetain() { if (DEBUG) Log.v(TAG, "Retaining: " + this); + if (!mStarted) { + RuntimeException e = new RuntimeException("here"); + e.fillInStackTrace(); + Log.w(TAG, "Called doRetain when not started: " + this, e); + return; + } mRetaining = true; mStarted = false; diff --git a/core/java/android/app/QueuedWork.java b/core/java/android/app/QueuedWork.java index af6bb1b..6ee4780 100644 --- a/core/java/android/app/QueuedWork.java +++ b/core/java/android/app/QueuedWork.java @@ -88,4 +88,14 @@ public class QueuedWork { toFinish.run(); } } + + /** + * Returns true if there is pending work to be done. Note that the + * result is out of data as soon as you receive it, so be careful how you + * use it. + */ + public static boolean hasPendingWork() { + return !sPendingWorkFinishers.isEmpty(); + } + } diff --git a/core/java/android/bluetooth/BluetoothAssignedNumbers.java b/core/java/android/bluetooth/BluetoothAssignedNumbers.java index 55bc814..580e9ff 100644 --- a/core/java/android/bluetooth/BluetoothAssignedNumbers.java +++ b/core/java/android/bluetooth/BluetoothAssignedNumbers.java @@ -23,12 +23,10 @@ package android.bluetooth; * @see <a href="https://www.bluetooth.org/technical/assignednumbers/identifiers.htm"> * The Official Bluetooth SIG Member Website | Company Identifiers</a> * - * @hide */ public class BluetoothAssignedNumbers { - //// Bluetooth SIG Company ID values - + // Bluetooth SIG Company ID values /* * Ericsson Technology Licensing. */ diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java index fd8f930..0c8e4d9 100644 --- a/core/java/android/bluetooth/BluetoothDeviceProfileState.java +++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java @@ -80,6 +80,7 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine public static final int UNPAIR = 100; public static final int AUTO_CONNECT_PROFILES = 101; public static final int TRANSITION_TO_STABLE = 102; + public static final int CONNECT_OTHER_PROFILES = 103; private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs @@ -149,10 +150,6 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine sendMessage(TRANSITION_TO_STABLE); } } else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { - if (!getCurrentState().equals(mBondedDevice)) { - Log.e(TAG, "State is: " + getCurrentState()); - return; - } Message msg = new Message(); msg.what = AUTO_CONNECT_PROFILES; sendMessageDelayed(msg, AUTO_CONNECT_DELAY); @@ -330,6 +327,27 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine } } break; + case CONNECT_OTHER_PROFILES: + if (isPhoneDocked(mDevice)) { + break; + } + if (message.arg1 == CONNECT_A2DP_OUTGOING) { + if (mA2dpService != null && + mA2dpService.getConnectedDevices().size() == 0) { + Log.i(TAG, "A2dp:Connect Other Profiles"); + mA2dpService.connect(mDevice); + } + } else if (message.arg1 == CONNECT_HFP_OUTGOING) { + if (mHeadsetService == null) { + deferMessage(message); + } else { + if (mHeadsetService.getConnectedDevices().size() == 0) { + Log.i(TAG, "Headset:Connect Other Profiles"); + mHeadsetService.connect(mDevice); + } + } + } + break; case TRANSITION_TO_STABLE: // ignore. break; @@ -440,6 +458,7 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine case DISCONNECT_PBAP_OUTGOING: case UNPAIR: case AUTO_CONNECT_PROFILES: + case CONNECT_OTHER_PROFILES: deferMessage(message); break; case TRANSITION_TO_STABLE: @@ -519,6 +538,7 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine case DISCONNECT_PBAP_OUTGOING: case UNPAIR: case AUTO_CONNECT_PROFILES: + case CONNECT_OTHER_PROFILES: deferMessage(message); break; case TRANSITION_TO_STABLE: @@ -628,6 +648,7 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine case DISCONNECT_PBAP_OUTGOING: case UNPAIR: case AUTO_CONNECT_PROFILES: + case CONNECT_OTHER_PROFILES: deferMessage(message); break; case TRANSITION_TO_STABLE: @@ -705,6 +726,7 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine case DISCONNECT_PBAP_OUTGOING: case UNPAIR: case AUTO_CONNECT_PROFILES: + case CONNECT_OTHER_PROFILES: deferMessage(message); break; case TRANSITION_TO_STABLE: @@ -890,6 +912,7 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) { return mHeadsetService.acceptIncomingConnect(mDevice); } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) { + handleConnectionOfOtherProfiles(command); return mHeadsetService.createIncomingConnect(mDevice); } break; @@ -899,6 +922,7 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine } break; case CONNECT_A2DP_INCOMING: + handleConnectionOfOtherProfiles(command); // ignore, Bluez takes care return true; case CONNECT_HID_OUTGOING: @@ -960,6 +984,60 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine return false; } + private void handleConnectionOfOtherProfiles(int command) { + // The white paper recommendations mentions that when there is a + // link loss, it is the responsibility of the remote device to connect. + // Many connect only 1 profile - and they connect the second profile on + // some user action (like play being pressed) and so we need this code. + // Auto Connect code only connects to the last connected device - which + // is useful in cases like when the phone reboots. But consider the + // following case: + // User is connected to the car's phone and A2DP profile. + // User comes to the desk and places the phone in the dock + // (or any speaker or music system or even another headset) and thus + // gets connected to the A2DP profile. User goes back to the car. + // Ideally the car's system is supposed to send incoming connections + // from both Handsfree and A2DP profile. But they don't. The Auto + // connect code, will not work here because we only auto connect to the + // last connected device for that profile which in this case is the dock. + // Now suppose a user is using 2 headsets simultaneously, one for the + // phone profile one for the A2DP profile. If this is the use case, we + // expect the user to use the preference to turn off the A2DP profile in + // the Settings screen for the first headset. Else, after link loss, + // there can be an incoming connection from the first headset which + // might result in the connection of the A2DP profile (if the second + // headset is slower) and thus the A2DP profile on the second headset + // will never get connected. + // + // TODO(): Handle other profiles here. + switch (command) { + case CONNECT_HFP_INCOMING: + // Connect A2DP if there is no incoming connection + // If the priority is OFF - don't auto connect. + // If the priority is AUTO_CONNECT, auto connect code takes care. + if (mA2dpService.getPriority(mDevice) == BluetoothProfile.PRIORITY_ON) { + Message msg = new Message(); + msg.what = CONNECT_OTHER_PROFILES; + msg.arg1 = CONNECT_A2DP_OUTGOING; + sendMessageDelayed(msg, AUTO_CONNECT_DELAY); + } + break; + case CONNECT_A2DP_INCOMING: + // This is again against spec. HFP incoming connections should be made + // before A2DP, so we should not hit this case. But many devices + // don't follow this. + if (mHeadsetService.getPriority(mDevice) == BluetoothProfile.PRIORITY_ON) { + Message msg = new Message(); + msg.what = CONNECT_OTHER_PROFILES; + msg.arg1 = CONNECT_HFP_OUTGOING; + sendMessageDelayed(msg, AUTO_CONNECT_DELAY); + } + break; + default: + break; + } + + } /*package*/ BluetoothDevice getDevice() { return mDevice; diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index c64fdbe..c72be6b 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -86,12 +86,33 @@ public final class BluetoothHeadset implements BluetoothProfile { /** - * Broadcast Action: Indicates a headset has posted a vendor-specific event. - * <p>Always contains the extra fields {@link #EXTRA_DEVICE}, - * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD}, and - * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS}. + * Intent used to broadcast that the headset has posted a + * vendor-specific event. + * + * <p>This intent will have 4 extras and 1 category. + * {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device + * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor specific + * command + * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT command + * type. + * Can be one of {@link #AT_CMD_TYPE_READ}, {@link #AT_CMD_TYPE_TEST}, + * or {@link #AT_CMD_TYPE_SET}, {@link #AT_CMD_TYPE_BASIC}, + * {@link #AT_CMD_TYPE_ACTION}. + * + * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command arguments. + * + * The category is the Company ID of the vendor defining the + * vendor-specific command. {@link BluetoothAssignedNumbers} + * + * For example, for Plantronics specific events + * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55 + * + * <p> For example, an AT+XEVENT=foo,3 will get translated into + * EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT + * EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET + * EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 + * * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. - * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT = @@ -100,31 +121,68 @@ public final class BluetoothHeadset implements BluetoothProfile { /** * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} * intents that contains the name of the vendor-specific command. - * @hide */ public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD"; /** * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} - * intents that contains the Company ID of the vendor defining the vendor-specific - * command. - * @see <a href="https://www.bluetooth.org/Technical/AssignedNumbers/identifiers.htm"> - * Bluetooth SIG Assigned Numbers - Company Identifiers</a> - * @hide + * intents that contains the AT command type of the vendor-specific command. + */ + public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = + "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE"; + + /** + * AT command type READ used with + * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} + * For example, AT+VGM?. There are no arguments for this command type. + */ + public static final int AT_CMD_TYPE_READ = 0; + + /** + * AT command type TEST used with + * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} + * For example, AT+VGM=?. There are no arguments for this command type. + */ + public static final int AT_CMD_TYPE_TEST = 1; + + /** + * AT command type SET used with + * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} + * For example, AT+VGM=<args>. + */ + public static final int AT_CMD_TYPE_SET = 2; + + /** + * AT command type BASIC used with + * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} + * For example, ATD. Single character commands and everything following the + * character are arguments. */ - public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID = - "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID"; + public static final int AT_CMD_TYPE_BASIC = 3; + + /** + * AT command type ACTION used with + * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} + * For example, AT+CHUP. There are no arguments for action commands. + */ + public static final int AT_CMD_TYPE_ACTION = 4; /** * A Parcelable String array extra field in * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains * the arguments to the vendor-specific command. - * @hide */ public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS"; + /** + * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} + * for the companyId + */ + public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY = + "android.bluetooth.headset.intent.category.companyid"; + /* * Headset state when SCO audio is connected * This state can be one of diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl index f0252b7..1eb269d 100644 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -92,4 +92,6 @@ interface IBluetooth List<BluetoothDevice> getConnectedPanDevices(); boolean connectPanDevice(in BluetoothDevice device); boolean disconnectPanDevice(in BluetoothDevice device); + + void sendConnectionStateChange(in BluetoothDevice device, int state, int prevState); } diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java index b63d026..5939643 100644 --- a/core/java/android/content/BroadcastReceiver.java +++ b/core/java/android/content/BroadcastReceiver.java @@ -17,11 +17,14 @@ package android.content; import android.app.ActivityManagerNative; +import android.app.ActivityThread; import android.app.IActivityManager; +import android.app.QueuedWork; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; +import android.util.Slog; /** * Base class for code that will receive intents sent by sendBroadcast(). @@ -160,6 +163,226 @@ import android.util.Log; * the containing process active for the entire time of your operation. */ public abstract class BroadcastReceiver { + private PendingResult mPendingResult; + private boolean mDebugUnregister; + + /** + * State for a result that is pending for a broadcast receiver. Returned + * by {@link BroadcastReceiver#goAsync() goAsync()} + * while in {@link BroadcastReceiver#onReceive BroadcastReceiver.onReceive()}. + */ + public static class PendingResult { + /** @hide */ + public static final int TYPE_COMPONENT = 0; + /** @hide */ + public static final int TYPE_REGISTERED = 1; + /** @hide */ + public static final int TYPE_UNREGISTERED = 2; + + final int mType; + final boolean mOrderedHint; + final boolean mInitialStickyHint; + final IBinder mToken; + + int mResultCode; + String mResultData; + Bundle mResultExtras; + boolean mAbortBroadcast; + boolean mFinished; + + /** @hide */ + public PendingResult(int resultCode, String resultData, Bundle resultExtras, + int type, boolean ordered, boolean sticky, IBinder token) { + mResultCode = resultCode; + mResultData = resultData; + mResultExtras = resultExtras; + mType = type; + mOrderedHint = ordered; + mInitialStickyHint = sticky; + mToken = token; + } + + /** + * Version of {@link BroadcastReceiver#setResultCode(int) + * BroadcastReceiver.setResultCode(int)} for + * asynchronous broadcast handling. + */ + public final void setResultCode(int code) { + checkSynchronousHint(); + mResultCode = code; + } + + /** + * Version of {@link BroadcastReceiver#getResultCode() + * BroadcastReceiver.getResultCode()} for + * asynchronous broadcast handling. + */ + public final int getResultCode() { + return mResultCode; + } + + /** + * Version of {@link BroadcastReceiver#setResultData(String) + * BroadcastReceiver.setResultData(String)} for + * asynchronous broadcast handling. + */ + public final void setResultData(String data) { + checkSynchronousHint(); + mResultData = data; + } + + /** + * Version of {@link BroadcastReceiver#getResultData() + * BroadcastReceiver.getResultData()} for + * asynchronous broadcast handling. + */ + public final String getResultData() { + return mResultData; + } + + /** + * Version of {@link BroadcastReceiver#setResultExtras(Bundle) + * BroadcastReceiver.setResultExtras(Bundle)} for + * asynchronous broadcast handling. + */ + public final void setResultExtras(Bundle extras) { + checkSynchronousHint(); + mResultExtras = extras; + } + + /** + * Version of {@link BroadcastReceiver#getResultExtras(boolean) + * BroadcastReceiver.getResultExtras(boolean)} for + * asynchronous broadcast handling. + */ + public final Bundle getResultExtras(boolean makeMap) { + Bundle e = mResultExtras; + if (!makeMap) return e; + if (e == null) mResultExtras = e = new Bundle(); + return e; + } + + /** + * Version of {@link BroadcastReceiver#setResult(int, String, Bundle) + * BroadcastReceiver.setResult(int, String, Bundle)} for + * asynchronous broadcast handling. + */ + public final void setResult(int code, String data, Bundle extras) { + checkSynchronousHint(); + mResultCode = code; + mResultData = data; + mResultExtras = extras; + } + + /** + * Version of {@link BroadcastReceiver#getAbortBroadcast() + * BroadcastReceiver.getAbortBroadcast()} for + * asynchronous broadcast handling. + */ + public final boolean getAbortBroadcast() { + return mAbortBroadcast; + } + + /** + * Version of {@link BroadcastReceiver#abortBroadcast() + * BroadcastReceiver.abortBroadcast()} for + * asynchronous broadcast handling. + */ + public final void abortBroadcast() { + checkSynchronousHint(); + mAbortBroadcast = true; + } + + /** + * Version of {@link BroadcastReceiver#clearAbortBroadcast() + * BroadcastReceiver.clearAbortBroadcast()} for + * asynchronous broadcast handling. + */ + public final void clearAbortBroadcast() { + mAbortBroadcast = false; + } + + /** + * Finish the broadcast. The current result will be sent and the + * next broadcast will proceed. + */ + public final void finish() { + if (mType == TYPE_COMPONENT) { + final IActivityManager mgr = ActivityManagerNative.getDefault(); + if (QueuedWork.hasPendingWork()) { + // If this is a broadcast component, we need to make sure any + // queued work is complete before telling AM we are done, so + // we don't have our process killed before that. We now know + // there is pending work; put another piece of work at the end + // of the list to finish the broadcast, so we don't block this + // thread (which may be the main thread) to have it finished. + // + // Note that we don't need to use QueuedWork.add() with the + // runnable, since we know the AM is waiting for us until the + // executor gets to it. + QueuedWork.singleThreadExecutor().execute( new Runnable() { + @Override public void run() { + if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, + "Finishing broadcast after work to component " + mToken); + sendFinished(mgr); + } + }); + } else { + if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, + "Finishing broadcast to component " + mToken); + sendFinished(mgr); + } + } else if (mOrderedHint && mType != TYPE_UNREGISTERED) { + if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, + "Finishing broadcast to " + mToken); + final IActivityManager mgr = ActivityManagerNative.getDefault(); + sendFinished(mgr); + } + } + + /** @hide */ + public void setExtrasClassLoader(ClassLoader cl) { + if (mResultExtras != null) { + mResultExtras.setClassLoader(cl); + } + } + + /** @hide */ + public void sendFinished(IActivityManager am) { + synchronized (this) { + if (mFinished) { + throw new IllegalStateException("Broadcast already finished"); + } + mFinished = true; + + try { + if (mOrderedHint) { + am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras, + mAbortBroadcast); + } else { + // This broadcast was sent to a component; it is not ordered, + // but we still need to tell the activity manager we are done. + am.finishReceiver(mToken, 0, null, null, false); + } + } catch (RemoteException ex) { + } + } + } + + void checkSynchronousHint() { + // Note that we don't assert when receiving the initial sticky value, + // since that may have come from an ordered broadcast. We'll catch + // them later when the real broadcast happens again. + if (mOrderedHint || mInitialStickyHint) { + return; + } + RuntimeException e = new RuntimeException( + "BroadcastReceiver trying to return result during a non-ordered broadcast"); + e.fillInStackTrace(); + Log.e("BroadcastReceiver", e.getMessage(), e); + } + } + public BroadcastReceiver() { } @@ -197,6 +420,26 @@ public abstract class BroadcastReceiver { public abstract void onReceive(Context context, Intent intent); /** + * This can be called by an application in {@link #onReceive} to allow + * it to keep the broadcast active after returning from that function. + * This does <em>not</em> change the expectation of being relatively + * responsive to the broadcast (finishing it within 10s), but does allow + * the implementation to move work related to it over to another thread + * to avoid glitching the main UI thread due to disk IO. + * + * @return Returns a {@link PendingResult} representing the result of + * the active broadcast. The BroadcastRecord itself is no longer active; + * all data and other interaction must go through {@link PendingResult} + * APIs. The {@link PendingResult#finish PendingResult.finish()} method + * must be called once processing of the broadcast is done. + */ + public final PendingResult goAsync() { + PendingResult res = mPendingResult; + mPendingResult = null; + return res; + } + + /** * Provide a binder to an already-running service. This method is synchronous * and will not start the target service if it is not present, so it is safe * to call from {@link #onReceive}. @@ -225,9 +468,9 @@ public abstract class BroadcastReceiver { * {@link android.app.Activity#RESULT_OK} constants, though the * actual meaning of this value is ultimately up to the broadcaster. * - * <p><strong>This method does not work with non-ordered broadcasts such + * <p class="note">This method does not work with non-ordered broadcasts such * as those sent with {@link Context#sendBroadcast(Intent) - * Context.sendBroadcast}</strong></p> + * Context.sendBroadcast}</p> * * @param code The new result code. * @@ -235,7 +478,7 @@ public abstract class BroadcastReceiver { */ public final void setResultCode(int code) { checkSynchronousHint(); - mResultCode = code; + mPendingResult.mResultCode = code; } /** @@ -244,7 +487,7 @@ public abstract class BroadcastReceiver { * @return int The current result code. */ public final int getResultCode() { - return mResultCode; + return mPendingResult != null ? mPendingResult.mResultCode : 0; } /** @@ -264,7 +507,7 @@ public abstract class BroadcastReceiver { */ public final void setResultData(String data) { checkSynchronousHint(); - mResultData = data; + mPendingResult.mResultData = data; } /** @@ -274,7 +517,7 @@ public abstract class BroadcastReceiver { * @return String The current result data; may be null. */ public final String getResultData() { - return mResultData; + return mPendingResult != null ? mPendingResult.mResultData : null; } /** @@ -296,7 +539,7 @@ public abstract class BroadcastReceiver { */ public final void setResultExtras(Bundle extras) { checkSynchronousHint(); - mResultExtras = extras; + mPendingResult.mResultExtras = extras; } /** @@ -311,9 +554,12 @@ public abstract class BroadcastReceiver { * @return Map The current extras map. */ public final Bundle getResultExtras(boolean makeMap) { - Bundle e = mResultExtras; + if (mPendingResult == null) { + return null; + } + Bundle e = mPendingResult.mResultExtras; if (!makeMap) return e; - if (e == null) mResultExtras = e = new Bundle(); + if (e == null) mPendingResult.mResultExtras = e = new Bundle(); return e; } @@ -341,9 +587,9 @@ public abstract class BroadcastReceiver { */ public final void setResult(int code, String data, Bundle extras) { checkSynchronousHint(); - mResultCode = code; - mResultData = data; - mResultExtras = extras; + mPendingResult.mResultCode = code; + mPendingResult.mResultData = data; + mPendingResult.mResultExtras = extras; } /** @@ -353,7 +599,7 @@ public abstract class BroadcastReceiver { * @return True if the broadcast should be aborted. */ public final boolean getAbortBroadcast() { - return mAbortBroadcast; + return mPendingResult != null ? mPendingResult.mAbortBroadcast : false; } /** @@ -372,7 +618,7 @@ public abstract class BroadcastReceiver { */ public final void abortBroadcast() { checkSynchronousHint(); - mAbortBroadcast = true; + mPendingResult.mAbortBroadcast = true; } /** @@ -380,7 +626,9 @@ public abstract class BroadcastReceiver { * broadcast. */ public final void clearAbortBroadcast() { - mAbortBroadcast = false; + if (mPendingResult != null) { + mPendingResult.mAbortBroadcast = false; + } } /** @@ -388,7 +636,7 @@ public abstract class BroadcastReceiver { * broadcast. */ public final boolean isOrderedBroadcast() { - return mOrderedHint; + return mPendingResult != null ? mPendingResult.mOrderedHint : false; } /** @@ -398,7 +646,7 @@ public abstract class BroadcastReceiver { * not directly the result of a broadcast right now. */ public final boolean isInitialStickyBroadcast() { - return mInitialStickyHint; + return mPendingResult != null ? mPendingResult.mInitialStickyHint : false; } /** @@ -406,15 +654,21 @@ public abstract class BroadcastReceiver { * running in ordered mode. */ public final void setOrderedHint(boolean isOrdered) { - mOrderedHint = isOrdered; + // Accidentally left in the SDK. } /** - * For internal use, sets the hint about whether this BroadcastReceiver is - * receiving the initial sticky broadcast value. @hide + * For internal use to set the result data that is active. @hide */ - public final void setInitialStickyHint(boolean isInitialSticky) { - mInitialStickyHint = isInitialSticky; + public final void setPendingResult(PendingResult result) { + mPendingResult = result; + } + + /** + * For internal use to set the result data that is active. @hide + */ + public final PendingResult getPendingResult() { + return mPendingResult; } /** @@ -440,10 +694,14 @@ public abstract class BroadcastReceiver { } void checkSynchronousHint() { + if (mPendingResult == null) { + throw new IllegalStateException("Call while result is not pending"); + } + // Note that we don't assert when receiving the initial sticky value, // since that may have come from an ordered broadcast. We'll catch // them later when the real broadcast happens again. - if (mOrderedHint || mInitialStickyHint) { + if (mPendingResult.mOrderedHint || mPendingResult.mInitialStickyHint) { return; } RuntimeException e = new RuntimeException( @@ -451,13 +709,5 @@ public abstract class BroadcastReceiver { e.fillInStackTrace(); Log.e("BroadcastReceiver", e.getMessage(), e); } - - private int mResultCode; - private String mResultData; - private Bundle mResultExtras; - private boolean mAbortBroadcast; - private boolean mDebugUnregister; - private boolean mOrderedHint; - private boolean mInitialStickyHint; } diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java index a19b132..6f4d098 100644 --- a/core/java/android/content/ClipData.java +++ b/core/java/android/content/ClipData.java @@ -37,8 +37,9 @@ import java.util.ArrayList; * each of which can hold one or more representations of an item of data. * For display to the user, it also has a label and iconic representation.</p> * - * <p>A ClipData is a sub-class of {@link ClipDescription}, which describes - * important meta-data about the clip. In particular, its {@link #getMimeType(int)} + * <p>A ClipData contains a {@link ClipDescription}, which describes + * important meta-data about the clip. In particular, its + * {@link ClipDescription#getMimeType(int) getDescription().getMimeType(int)} * must return correct MIME type(s) describing the data in the clip. For help * in correctly constructing a clip with the correct MIME type, use * {@link #newPlainText(CharSequence, Bitmap, CharSequence)}, @@ -62,8 +63,8 @@ import java.util.ArrayList; * <p>If all you want is the textual representation of the clipped data, you * can use the convenience method {@link Item#coerceToText Item.coerceToText}. * In this case there is generally no need to worry about the MIME types - * reported by {@link #getMimeType(int)}, since any clip item an always be - * converted to a string. + * reported by {@link ClipDescription#getMimeType(int) getDescription().getMimeType(int)}, + * since any clip item an always be converted to a string. * * <p>More complicated exchanges will be done through URIs, in particular * "content:" URIs. A content URI allows the recipient of a ClippedData item @@ -133,11 +134,16 @@ import java.util.ArrayList; * into an editor), then {@link Item#coerceToText(Context)} will ask the content * provider for the clip URI as text and successfully paste the entire note. */ -public class ClipData extends ClipDescription { - static final String[] MIMETYPES_TEXT_PLAIN = new String[] { MIMETYPE_TEXT_PLAIN }; - static final String[] MIMETYPES_TEXT_URILIST = new String[] { MIMETYPE_TEXT_URILIST }; - static final String[] MIMETYPES_TEXT_INTENT = new String[] { MIMETYPE_TEXT_INTENT }; - +public class ClipData implements Parcelable { + static final String[] MIMETYPES_TEXT_PLAIN = new String[] { + ClipDescription.MIMETYPE_TEXT_PLAIN }; + static final String[] MIMETYPES_TEXT_URILIST = new String[] { + ClipDescription.MIMETYPE_TEXT_URILIST }; + static final String[] MIMETYPES_TEXT_INTENT = new String[] { + ClipDescription.MIMETYPE_TEXT_INTENT }; + + final ClipDescription mClipDescription; + final Bitmap mIcon; final ArrayList<Item> mItems = new ArrayList<Item>(); @@ -320,7 +326,24 @@ public class ClipData extends ClipDescription { * @param item The contents of the first item in the clip. */ public ClipData(CharSequence label, String[] mimeTypes, Bitmap icon, Item item) { - super(label, mimeTypes); + mClipDescription = new ClipDescription(label, mimeTypes); + if (item == null) { + throw new NullPointerException("item is null"); + } + mIcon = icon; + mItems.add(item); + } + + /** + * Create a new clip. + * + * @param description The ClipDescription describing the clip contents. + * @param icon Bitmap providing the user with an iconing representation of + * the clip. + * @param item The contents of the first item in the clip. + */ + public ClipData(ClipDescription description, Bitmap icon, Item item) { + mClipDescription = description; if (item == null) { throw new NullPointerException("item is null"); } @@ -329,7 +352,8 @@ public class ClipData extends ClipDescription { } /** - * Create a new ClipData holding data of the type {@link #MIMETYPE_TEXT_PLAIN}. + * Create a new ClipData holding data of the type + * {@link ClipDescription#MIMETYPE_TEXT_PLAIN}. * * @param label User-visible label for the clip data. * @param icon Iconic representation of the clip data. @@ -342,7 +366,8 @@ public class ClipData extends ClipDescription { } /** - * Create a new ClipData holding an Intent with MIME type {@link #MIMETYPE_TEXT_INTENT}. + * Create a new ClipData holding an Intent with MIME type + * {@link ClipDescription#MIMETYPE_TEXT_INTENT}. * * @param label User-visible label for the clip data. * @param icon Iconic representation of the clip data. @@ -358,7 +383,7 @@ public class ClipData extends ClipDescription { * Create a new ClipData holding a URI. If the URI is a content: URI, * this will query the content provider for the MIME type of its data and * use that as the MIME type. Otherwise, it will use the MIME type - * {@link #MIMETYPE_TEXT_URILIST}. + * {@link ClipDescription#MIMETYPE_TEXT_URILIST}. * * @param resolver ContentResolver used to get information about the URI. * @param label User-visible label for the clip data. @@ -375,7 +400,7 @@ public class ClipData extends ClipDescription { mimeTypes = resolver.getStreamTypes(uri, "*/*"); if (mimeTypes == null) { if (realType != null) { - mimeTypes = new String[] { realType, MIMETYPE_TEXT_URILIST }; + mimeTypes = new String[] { realType, ClipDescription.MIMETYPE_TEXT_URILIST }; } } else { String[] tmp = new String[mimeTypes.length + (realType != null ? 2 : 1)]; @@ -385,7 +410,7 @@ public class ClipData extends ClipDescription { i++; } System.arraycopy(mimeTypes, 0, tmp, i, mimeTypes.length); - tmp[i + mimeTypes.length] = MIMETYPE_TEXT_URILIST; + tmp[i + mimeTypes.length] = ClipDescription.MIMETYPE_TEXT_URILIST; mimeTypes = tmp; } } @@ -396,7 +421,8 @@ public class ClipData extends ClipDescription { } /** - * Create a new ClipData holding an URI with MIME type {@link #MIMETYPE_TEXT_URILIST}. + * Create a new ClipData holding an URI with MIME type + * {@link ClipDescription#MIMETYPE_TEXT_URILIST}. * Unlike {@link #newUri(ContentResolver, CharSequence, Bitmap, Uri)}, nothing * is inferred about the URI -- if it is a content: URI holding a bitmap, * the reported type will still be uri-list. Use this with care! @@ -411,6 +437,14 @@ public class ClipData extends ClipDescription { return new ClipData(label, MIMETYPES_TEXT_URILIST, icon, item); } + /** + * Return the {@link ClipDescription} associated with this data, describing + * what it contains. + */ + public ClipDescription getDescription() { + return mClipDescription; + } + public void addItem(Item item) { if (item == null) { throw new NullPointerException("item is null"); @@ -437,7 +471,7 @@ public class ClipData extends ClipDescription { @Override public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); + mClipDescription.writeToParcel(dest, flags); if (mIcon != null) { dest.writeInt(1); mIcon.writeToParcel(dest, flags); @@ -465,7 +499,7 @@ public class ClipData extends ClipDescription { } ClipData(Parcel in) { - super(in); + mClipDescription = new ClipDescription(in); if (in.readInt() != 0) { mIcon = Bitmap.CREATOR.createFromParcel(in); } else { diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index c9115c5..34bd386 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -728,10 +728,16 @@ public class SyncManager implements OnAccountsUpdateListener { newDelayInMs = maxSyncRetryTimeInSeconds * 1000; } + final long backoff = now + newDelayInMs; + mSyncStorageEngine.setBackoff(op.account, op.authority, - now + newDelayInMs, newDelayInMs); + backoff, newDelayInMs); + + op.backoff = backoff; + op.updateEffectiveRunTime(); + synchronized (mSyncQueue) { - mSyncQueue.onBackoffChanged(op.account, op.authority, now + newDelayInMs); + mSyncQueue.onBackoffChanged(op.account, op.authority, backoff); } } diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java index 96e6f22..93f9a71 100644 --- a/core/java/android/database/sqlite/SQLiteClosable.java +++ b/core/java/android/database/sqlite/SQLiteClosable.java @@ -23,14 +23,12 @@ import android.database.CursorWindow; */ public abstract class SQLiteClosable { private int mReferenceCount = 1; - private Object mLock = new Object(); // STOPSHIP remove this line protected abstract void onAllReferencesReleased(); protected void onAllReferencesReleasedFromContainer() {} public void acquireReference() { - synchronized(mLock) { // STOPSHIP change 'mLock' to 'this' - checkRefCount(); + synchronized(this) { if (mReferenceCount <= 0) { throw new IllegalStateException( "attempt to re-open an already-closed object: " + getObjInfo()); @@ -40,8 +38,7 @@ public abstract class SQLiteClosable { } public void releaseReference() { - synchronized(mLock) { // STOPSHIP change 'mLock' to 'this' - checkRefCount(); + synchronized(this) { mReferenceCount--; if (mReferenceCount == 0) { onAllReferencesReleased(); @@ -50,8 +47,7 @@ public abstract class SQLiteClosable { } public void releaseReferenceFromContainer() { - synchronized(mLock) { // STOPSHIP change 'mLock' to 'this' - checkRefCount(); + synchronized(this) { mReferenceCount--; if (mReferenceCount == 0) { onAllReferencesReleasedFromContainer(); @@ -76,12 +72,4 @@ public abstract class SQLiteClosable { buff.append(") "); return buff.toString(); } - - // STOPSHIP remove this method before shipping - private void checkRefCount() { - if (mReferenceCount > 1000) { - throw new IllegalStateException("bad refcount: " + mReferenceCount + - ". file bug against frameworks->database" + getObjInfo()); - } - } } diff --git a/core/java/android/nfc/ErrorCodes.java b/core/java/android/nfc/ErrorCodes.java index 5b76d84..69329df 100644 --- a/core/java/android/nfc/ErrorCodes.java +++ b/core/java/android/nfc/ErrorCodes.java @@ -33,6 +33,34 @@ public class ErrorCodes { } } + public static String asString(int code) { + switch (code) { + case SUCCESS: return "SUCCESS"; + case ERROR_IO: return "IO"; + case ERROR_CANCELLED: return "CANCELLED"; + case ERROR_TIMEOUT: return "TIMEOUT"; + case ERROR_BUSY: return "BUSY"; + case ERROR_CONNECT: return "CONNECT/DISCONNECT"; +// case ERROR_DISCONNECT: return "DISCONNECT"; + case ERROR_READ: return "READ"; + case ERROR_WRITE: return "WRITE"; + case ERROR_INVALID_PARAM: return "INVALID_PARAM"; + case ERROR_INSUFFICIENT_RESOURCES: return "INSUFFICIENT_RESOURCES"; + case ERROR_SOCKET_CREATION: return "SOCKET_CREATION"; + case ERROR_SOCKET_NOT_CONNECTED: return "SOCKET_NOT_CONNECTED"; + case ERROR_BUFFER_TO_SMALL: return "BUFFER_TO_SMALL"; + case ERROR_SAP_USED: return "SAP_USED"; + case ERROR_SERVICE_NAME_USED: return "SERVICE_NAME_USED"; + case ERROR_SOCKET_OPTIONS: return "SOCKET_OPTIONS"; + case ERROR_NFC_ON: return "NFC_ON"; + case ERROR_NOT_INITIALIZED: return "NOT_INITIALIZED"; + case ERROR_SE_ALREADY_SELECTED: return "SE_ALREADY_SELECTED"; + case ERROR_SE_CONNECTED: return "SE_CONNECTED"; + case ERROR_NO_SE_CONNECTED: return "NO_SE_CONNECTED"; + default: return "UNKNOWN ERROR"; + } + } + public static final int SUCCESS = 0; public static final int ERROR_IO = -1; diff --git a/core/java/android/nfc/NdefTagConnection.java b/core/java/android/nfc/NdefTagConnection.java index 321b0ec..27fa25c 100644 --- a/core/java/android/nfc/NdefTagConnection.java +++ b/core/java/android/nfc/NdefTagConnection.java @@ -42,8 +42,8 @@ public class NdefTagConnection extends RawTagConnection { * Internal constructor, to be used by NfcAdapter * @hide */ - /* package private */ NdefTagConnection(INfcAdapter service, NdefTag tag, String target) throws RemoteException { - super(service, tag); + /* package private */ NdefTagConnection(NfcAdapter adapter, NdefTag tag, String target) throws RemoteException { + super(adapter, tag); String[] targets = tag.getNdefTargets(); int i; @@ -63,8 +63,8 @@ public class NdefTagConnection extends RawTagConnection { * Internal constructor, to be used by NfcAdapter * @hide */ - /* package private */ NdefTagConnection(INfcAdapter service, NdefTag tag) throws RemoteException { - this(service, tag, tag.getNdefTargets()[0]); + /* package private */ NdefTagConnection(NfcAdapter adapter, NdefTag tag) throws RemoteException { + this(adapter, tag, tag.getNdefTargets()[0]); } /** @@ -97,7 +97,7 @@ public class NdefTagConnection extends RawTagConnection { msgArray[0] = msg; return msgArray; } catch (RemoteException e) { - Log.e(TAG, "NFC service died"); + attemptDeadServiceRecovery(e); return null; } } @@ -134,7 +134,7 @@ public class NdefTagConnection extends RawTagConnection { throw new IOException(); } } catch (RemoteException e) { - Log.e(TAG, "NFC service died"); + attemptDeadServiceRecovery(e); } } @@ -161,7 +161,7 @@ public class NdefTagConnection extends RawTagConnection { throw new IOException(); } } catch (RemoteException e) { - Log.e(TAG, "NFC service died"); + attemptDeadServiceRecovery(e); return false; } } @@ -188,7 +188,7 @@ public class NdefTagConnection extends RawTagConnection { return result; } catch (RemoteException e) { - Log.e(TAG, "NFC service died"); + attemptDeadServiceRecovery(e); return NDEF_MODE_UNKNOWN; } } diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index 7f4b4a2..a093d12 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -168,7 +168,10 @@ public final class NfcAdapter { private static boolean sIsInitialized = false; private static NfcAdapter sAdapter; - private final INfcAdapter mService; + // Final after construction, except for attemptDeadServiceRecovery() + // when NFC crashes. + // Not locked - we accept a best effort attempt when NFC crashes. + /*package*/ INfcAdapter mService; private NfcAdapter(INfcAdapter service) { mService = service; @@ -194,6 +197,16 @@ public final class NfcAdapter { } } + /** get handle to NFC service interface */ + private static synchronized INfcAdapter getServiceInterface() { + /* get a handle to NFC service */ + IBinder b = ServiceManager.getService("nfc"); + if (b == null) { + return null; + } + return INfcAdapter.Stub.asInterface(b); + } + /** * Get a handle to the default NFC Adapter on this Android device. * <p> @@ -214,18 +227,31 @@ public final class NfcAdapter { return null; } - /* get a handle to NFC service */ - IBinder b = ServiceManager.getService("nfc"); - if (b == null) { + INfcAdapter service = getServiceInterface(); + if (service == null) { Log.e(TAG, "could not retrieve NFC service"); return null; } - sAdapter = new NfcAdapter(INfcAdapter.Stub.asInterface(b)); + sAdapter = new NfcAdapter(service); return sAdapter; } } + /** NFC service dead - attempt best effort recovery */ + /*package*/ void attemptDeadServiceRecovery(Exception e) { + Log.e(TAG, "NFC service dead - attempting to recover", e); + INfcAdapter service = getServiceInterface(); + if (service == null) { + Log.e(TAG, "could not retrieve NFC service during service recovery"); + return; + } + /* assigning to mService is not thread-safe, but this is best-effort code + * and on a well-behaved system should never happen */ + mService = service; + return; + } + /** * Return true if this NFC Adapter has any features enabled. * <p> @@ -241,7 +267,7 @@ public final class NfcAdapter { try { return mService.isEnabled(); } catch (RemoteException e) { - Log.e(TAG, "RemoteException in isEnabled()", e); + attemptDeadServiceRecovery(e); return false; } } @@ -258,7 +284,7 @@ public final class NfcAdapter { try { return mService.enable(); } catch (RemoteException e) { - Log.e(TAG, "RemoteException in enable()", e); + attemptDeadServiceRecovery(e); return false; } } @@ -277,7 +303,7 @@ public final class NfcAdapter { try { return mService.disable(); } catch (RemoteException e) { - Log.e(TAG, "RemoteException in disable()", e); + attemptDeadServiceRecovery(e); return false; } } @@ -303,7 +329,7 @@ public final class NfcAdapter { try { mService.localSet(message); } catch (RemoteException e) { - Log.e(TAG, "NFC service died", e); + attemptDeadServiceRecovery(e); } } @@ -317,7 +343,7 @@ public final class NfcAdapter { try { return mService.localGet(); } catch (RemoteException e) { - Log.e(TAG, "NFC service died", e); + attemptDeadServiceRecovery(e); return null; } } @@ -331,9 +357,9 @@ public final class NfcAdapter { throw new IllegalArgumentException("mock tag cannot be used for connections"); } try { - return new RawTagConnection(mService, tag); + return new RawTagConnection(this, tag); } catch (RemoteException e) { - Log.e(TAG, "NFC service died", e); + attemptDeadServiceRecovery(e); return null; } } @@ -347,9 +373,9 @@ public final class NfcAdapter { throw new IllegalArgumentException("mock tag cannot be used for connections"); } try { - return new RawTagConnection(mService, tag, target); + return new RawTagConnection(this, tag, target); } catch (RemoteException e) { - Log.e(TAG, "NFC service died", e); + attemptDeadServiceRecovery(e); return null; } } @@ -363,9 +389,9 @@ public final class NfcAdapter { throw new IllegalArgumentException("mock tag cannot be used for connections"); } try { - return new NdefTagConnection(mService, tag); + return new NdefTagConnection(this, tag); } catch (RemoteException e) { - Log.e(TAG, "NFC service died", e); + attemptDeadServiceRecovery(e); return null; } } @@ -379,9 +405,9 @@ public final class NfcAdapter { throw new IllegalArgumentException("mock tag cannot be used for connections"); } try { - return new NdefTagConnection(mService, tag, target); + return new NdefTagConnection(this, tag, target); } catch (RemoteException e) { - Log.e(TAG, "NFC service died", e); + attemptDeadServiceRecovery(e); return null; } } diff --git a/core/java/android/nfc/RawTagConnection.java b/core/java/android/nfc/RawTagConnection.java index 8442893..24072e5 100644 --- a/core/java/android/nfc/RawTagConnection.java +++ b/core/java/android/nfc/RawTagConnection.java @@ -35,15 +35,20 @@ import android.util.Log; */ public class RawTagConnection { - /*package*/ final INfcAdapter mService; - /*package*/ final INfcTag mTagService; /*package*/ final Tag mTag; /*package*/ boolean mIsConnected; /*package*/ String mSelectedTarget; + private final NfcAdapter mAdapter; + + // Following fields are final after construction, except for + // during attemptDeadServiceRecovery() when NFC crashes. + // Not locked - we accept a best effort attempt when NFC crashes. + /*package*/ INfcAdapter mService; + /*package*/ INfcTag mTagService; private static final String TAG = "NFC"; - /* package private */ RawTagConnection(INfcAdapter service, Tag tag, String target) throws RemoteException { + /*package*/ RawTagConnection(NfcAdapter adapter, Tag tag, String target) throws RemoteException { String[] targets = tag.getRawTargets(); int i; @@ -58,14 +63,28 @@ public class RawTagConnection { throw new IllegalArgumentException(); } - mService = service; - mTagService = service.getNfcTagInterface(); + mAdapter = adapter; + mService = mAdapter.mService; + mTagService = mService.getNfcTagInterface(); mTag = tag; mSelectedTarget = target; } - /* package private */ RawTagConnection(INfcAdapter service, Tag tag) throws RemoteException { - this(service, tag, tag.getRawTargets()[0]); + /*package*/ RawTagConnection(NfcAdapter adapter, Tag tag) throws RemoteException { + this(adapter, tag, tag.getRawTargets()[0]); + } + + /** NFC service dead - attempt best effort recovery */ + /*package*/ void attemptDeadServiceRecovery(Exception e) { + mAdapter.attemptDeadServiceRecovery(e); + /* assigning to mService is not thread-safe, but this is best-effort code + * and on a well-behaved system should never happen */ + mService = mAdapter.mService; + try { + mTagService = mService.getNfcTagInterface(); + } catch (RemoteException e2) { + Log.e(TAG, "second RemoteException trying to recover from dead NFC service", e2); + } } /** @@ -101,7 +120,7 @@ public class RawTagConnection { try { return mTagService.isPresent(mTag.mServiceHandle); } catch (RemoteException e) { - Log.e(TAG, "NFC service died", e); + attemptDeadServiceRecovery(e); return false; } } @@ -136,7 +155,7 @@ public class RawTagConnection { try { mTagService.close(mTag.mServiceHandle); } catch (RemoteException e) { - Log.e(TAG, "NFC service died", e); + attemptDeadServiceRecovery(e); } } @@ -159,7 +178,7 @@ public class RawTagConnection { } return response; } catch (RemoteException e) { - Log.e(TAG, "NFC service died", e); + attemptDeadServiceRecovery(e); throw new IOException("NFC service died"); } } diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java index 247b281..c62715b 100644 --- a/core/java/android/os/BatteryManager.java +++ b/core/java/android/os/BatteryManager.java @@ -87,7 +87,7 @@ public class BatteryManager { /** * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}: - * Boolean value set to true if an unsupported charger is attached + * Int value set to nonzero if an unsupported charger is attached * to the device. * {@hide} */ diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index 0ec1c74..8eac7aa 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -181,6 +181,13 @@ public final class Bundle implements Parcelable, Cloneable { } /** + * Return the ClassLoader currently associated with this Bundle. + */ + public ClassLoader getClassLoader() { + return mClassLoader; + } + + /** * Clones the current Bundle. The internal map is cloned, but the keys and * values to which it refers are copied by reference. */ diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 9786959..40aceb3 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -173,6 +173,12 @@ public final class StrictMode { public static final int PENALTY_GATHER = 0x100; /** + * Mask of all the penalty bits. + */ + private static final int PENALTY_MASK = + PENALTY_LOG | PENALTY_DIALOG | PENALTY_DEATH | PENALTY_DROPBOX | PENALTY_GATHER; + + /** * The current VmPolicy in effect. */ private static volatile int sVmPolicyMask = 0; @@ -882,7 +888,7 @@ public final class StrictMode { } } - // The violationMask, passed to ActivityManager, is a + // The violationMaskSubset, passed to ActivityManager, is a // subset of the original StrictMode policy bitmask, with // only the bit violated and penalty bits to be executed // by the ActivityManagerService remaining set. @@ -900,7 +906,35 @@ public final class StrictMode { if (violationMaskSubset != 0) { int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage); violationMaskSubset |= violationBit; + final int violationMaskSubsetFinal = violationMaskSubset; final int savedPolicyMask = getThreadPolicyMask(); + + final boolean justDropBox = (info.policy & PENALTY_MASK) == PENALTY_DROPBOX; + if (justDropBox) { + // If all we're going to ask the activity manager + // to do is dropbox it (the common case during + // platform development), we can avoid doing this + // call synchronously which Binder data suggests + // isn't always super fast, despite the implementation + // in the ActivityManager trying to be mostly async. + new Thread("callActivityManagerForStrictModeDropbox") { + public void run() { + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + try { + ActivityManagerNative.getDefault(). + handleApplicationStrictModeViolation( + RuntimeInit.getApplicationObject(), + violationMaskSubsetFinal, + info); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException handling StrictMode violation", e); + } + } + }.start(); + return; + } + + // Normal synchronous call to the ActivityManager. try { // First, remove any policy before we call into the Activity Manager, // otherwise we'll infinite recurse as we try to log policy violations diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java index a59b2f8..0c6a237 100644 --- a/core/java/android/preference/PreferenceActivity.java +++ b/core/java/android/preference/PreferenceActivity.java @@ -893,13 +893,13 @@ public abstract class PreferenceActivity extends ListActivity implements } } - public void switchToHeaderInner(String fragmentName, Bundle args, boolean next) { + private void switchToHeaderInner(String fragmentName, Bundle args, int direction) { getFragmentManager().popBackStack(BACK_STACK_PREFS, POP_BACK_STACK_INCLUSIVE); Fragment f = Fragment.instantiate(this, fragmentName, args); FragmentTransaction transaction = getFragmentManager().openTransaction(); - transaction.setTransition(next ? - FragmentTransaction.TRANSIT_FRAGMENT_NEXT : - FragmentTransaction.TRANSIT_FRAGMENT_PREV); + transaction.setTransition(direction == 0 ? FragmentTransaction.TRANSIT_NONE + : direction > 0 ? FragmentTransaction.TRANSIT_FRAGMENT_NEXT + : FragmentTransaction.TRANSIT_FRAGMENT_PREV); transaction.replace(com.android.internal.R.id.prefs, f); transaction.commit(); } @@ -913,7 +913,7 @@ public abstract class PreferenceActivity extends ListActivity implements */ public void switchToHeader(String fragmentName, Bundle args) { setSelectedHeader(null); - switchToHeaderInner(fragmentName, args, true); + switchToHeaderInner(fragmentName, args, 0); } /** @@ -923,8 +923,8 @@ public abstract class PreferenceActivity extends ListActivity implements * @param header The new header to display. */ public void switchToHeader(Header header) { - switchToHeaderInner(header.fragment, header.fragmentArguments, - mHeaders.indexOf(header) > mHeaders.indexOf(mCurHeader)); + int direction = mHeaders.indexOf(header) - mHeaders.indexOf(mCurHeader); + switchToHeaderInner(header.fragment, header.fragmentArguments, direction); setSelectedHeader(header); } diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java index 7629c31..33f37f8 100644 --- a/core/java/android/preference/PreferenceFragment.java +++ b/core/java/android/preference/PreferenceFragment.java @@ -151,7 +151,7 @@ public abstract class PreferenceFragment extends Fragment implements @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(com.android.internal.R.layout.preference_list_content, + return inflater.inflate(com.android.internal.R.layout.preference_list_fragment, container, false); } diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 0448ec0..9e6434a 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -5127,6 +5127,47 @@ public final class ContactsContract { * <P>Type: TEXT</P> */ public static final String NAME = DATA; + + /** + * Return the string resource that best describes the given + * {@link #TYPE}. Will always return a valid resource. + */ + public static final int getTypeLabelResource(int type) { + switch (type) { + case TYPE_ASSISTANT: return com.android.internal.R.string.relationTypeAssistant; + case TYPE_BROTHER: return com.android.internal.R.string.relationTypeBrother; + case TYPE_CHILD: return com.android.internal.R.string.relationTypeChild; + case TYPE_DOMESTIC_PARTNER: + return com.android.internal.R.string.relationTypeDomesticPartner; + case TYPE_FATHER: return com.android.internal.R.string.relationTypeFather; + case TYPE_FRIEND: return com.android.internal.R.string.relationTypeFriend; + case TYPE_MANAGER: return com.android.internal.R.string.relationTypeManager; + case TYPE_MOTHER: return com.android.internal.R.string.relationTypeMother; + case TYPE_PARENT: return com.android.internal.R.string.relationTypeParent; + case TYPE_PARTNER: return com.android.internal.R.string.relationTypePartner; + case TYPE_REFERRED_BY: + return com.android.internal.R.string.relationTypeReferredBy; + case TYPE_RELATIVE: return com.android.internal.R.string.relationTypeRelative; + case TYPE_SISTER: return com.android.internal.R.string.relationTypeSister; + case TYPE_SPOUSE: return com.android.internal.R.string.relationTypeSpouse; + default: return com.android.internal.R.string.orgTypeCustom; + } + } + + /** + * Return a {@link CharSequence} that best describes the given type, + * possibly substituting the given {@link #LABEL} value + * for {@link #TYPE_CUSTOM}. + */ + public static final CharSequence getTypeLabel(Resources res, int type, + CharSequence label) { + if (type == TYPE_CUSTOM && !TextUtils.isEmpty(label)) { + return label; + } else { + final int labelRes = getTypeLabelResource(type); + return res.getText(labelRes); + } + } } /** diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java index c9e3b69..7054888 100644 --- a/core/java/android/provider/Downloads.java +++ b/core/java/android/provider/Downloads.java @@ -16,6 +16,7 @@ package android.provider; +import android.app.DownloadManager; import android.net.Uri; /** @@ -621,18 +622,19 @@ public final class Downloads { * This download is visible but only shows in the notifications * while it's in progress. */ - public static final int VISIBILITY_VISIBLE = 0; + public static final int VISIBILITY_VISIBLE = DownloadManager.Request.VISIBILITY_VISIBLE; /** * This download is visible and shows in the notifications while * in progress and after completion. */ - public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1; + public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = + DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED; /** * This download doesn't show in the UI or in the notifications. */ - public static final int VISIBILITY_HIDDEN = 2; + public static final int VISIBILITY_HIDDEN = DownloadManager.Request.VISIBILITY_HIDDEN; /** * Constants related to HTTP request headers associated with each download. diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 62d2ff2..2b79037 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -279,6 +279,20 @@ public final class Settings { "android.settings.INPUT_METHOD_SETTINGS"; /** + * Activity Action: Show enabler activity to enable/disable input methods and subtypes. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_INPUT_METHOD_AND_SUBTYPE_ENABLER = + "android.settings.INPUT_METHOD_AND_SUBTYPE_ENABLER"; + + /** * Activity Action: Show settings to manage the user input dictionary. * <p> * In some cases, a matching Activity may not exist, so ensure you diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java index 61e2305..b5e85a0 100644 --- a/core/java/android/server/BluetoothA2dpService.java +++ b/core/java/android/server/BluetoothA2dpService.java @@ -483,6 +483,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { if (name.equals(PROPERTY_STATE)) { int state = convertBluezSinkStringtoState(propValues[1]); + log("A2DP: onSinkPropertyChanged newState is: " + state + "mPlayingA2dpDevice: " + mPlayingA2dpDevice); + if (mAudioDevices.get(device) == null) { // This is for an incoming connection for a device not known to us. // We have authorized it and bluez state has changed. @@ -496,6 +498,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { handleSinkPlayingStateChange(device, BluetoothA2dp.STATE_NOT_PLAYING, BluetoothA2dp.STATE_PLAYING); } else { + mPlayingA2dpDevice = null; int prevState = mAudioDevices.get(device); handleSinkStateChange(device, prevState, state); } @@ -510,7 +513,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { mSinkCount--; } else if (state == BluetoothA2dp.STATE_CONNECTED) { mSinkCount ++; - mPlayingA2dpDevice = null; } mAudioDevices.put(device, state); @@ -534,6 +536,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { mContext.sendBroadcast(intent, BLUETOOTH_PERM); if (DBG) log("A2DP state : device: " + device + " State:" + prevState + "->" + state); + + mBluetoothService.sendConnectionStateChange(device, state, prevState); } } diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index f47c553..4f56281 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -253,6 +253,7 @@ class BluetoothEventLoop { // we filled up our cache. mBluetoothService.getAllProperties(); } + log("Property Changed: " + propValues[0] + " : " + propValues[1]); String name = propValues[0]; if (name.equals("Name")) { mBluetoothService.setProperty(name, propValues[1]); @@ -309,6 +310,8 @@ class BluetoothEventLoop { // Note: bluez only sends this property change when it restarts. if (propValues[1].equals("true")) onRestartRequired(); + } else if (name.equals("DiscoverableTimeout")) { + mBluetoothService.setProperty(name, propValues[1]); } } @@ -319,10 +322,9 @@ class BluetoothEventLoop { Log.e(TAG, "onDevicePropertyChanged: Address of the remote device in null"); return; } - if (DBG) { - log("Device property changed: " + address + " property: " - + name + " value: " + propValues[1]); - } + log("Device property changed: " + address + " property: " + + name + " value: " + propValues[1]); + BluetoothDevice device = mAdapter.getRemoteDevice(address); if (name.equals("Name")) { mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index 660f9ab..65047c0 100644 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -162,6 +162,7 @@ public class BluetoothService extends IBluetooth.Stub { private final HashMap<BluetoothDevice, Pair<Integer, String>> mPanDevices; private final HashMap<String, Pair<byte[], byte[]>> mDeviceOobData; + private int mProfilesConnected = 0, mProfilesConnecting = 0, mProfilesDisconnecting = 0; private static String mDockAddress; private String mDockPin; @@ -411,6 +412,10 @@ public class BluetoothService extends IBluetooth.Stub { mAdapterProperties.clear(); mServiceRecordToPid.clear(); + mProfilesConnected = 0; + mProfilesConnecting = 0; + mProfilesDisconnecting = 0; + if (saveSetting) { persistBluetoothOnSetting(false); } @@ -1563,6 +1568,7 @@ public class BluetoothService extends IBluetooth.Stub { mContext.sendBroadcast(intent, BLUETOOTH_PERM); if (DBG) log("Pan Device state : device: " + device + " State:" + prevState + "->" + state); + sendConnectionStateChange(device, state, prevState); } /*package*/ synchronized void handlePanDeviceStateChange(BluetoothDevice device, @@ -1790,7 +1796,7 @@ public class BluetoothService extends IBluetooth.Stub { mContext.sendBroadcast(intent, BLUETOOTH_PERM); if (DBG) log("InputDevice state : device: " + device + " State:" + prevState + "->" + state); - + sendConnectionStateChange(device, state, prevState); } /*package*/ void handleInputDevicePropertyChange(String address, boolean connected) { @@ -2731,6 +2737,75 @@ public class BluetoothService extends IBluetooth.Stub { } } + public synchronized void sendConnectionStateChange(BluetoothDevice device, int state, + int prevState) { + if (updateCountersAndCheckForConnectionStateChange(device, state, prevState)) { + state = getAdapterConnectionState(state); + prevState = getAdapterConnectionState(prevState); + Intent intent = new Intent(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + intent.putExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, state); + intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_CONNECTION_STATE, prevState); + mContext.sendBroadcast(intent, BLUETOOTH_PERM); + log("CONNECTION_STATE_CHANGE: " + device + ": " + prevState + "-> " + state); + } + } + + private int getAdapterConnectionState(int state) { + switch (state) { + case BluetoothProfile.STATE_CONNECTING: + return BluetoothAdapter.STATE_CONNECTING; + case BluetoothProfile.STATE_CONNECTED: + return BluetoothAdapter.STATE_CONNECTED; + case BluetoothProfile.STATE_DISCONNECTING: + return BluetoothAdapter.STATE_DISCONNECTING; + case BluetoothProfile.STATE_DISCONNECTED: + return BluetoothAdapter.STATE_DISCONNECTED; + default: + Log.e(TAG, "Error in getAdapterConnectionState"); + return -1; + } + } + + private boolean updateCountersAndCheckForConnectionStateChange(BluetoothDevice device, + int state, + int prevState) { + switch (state) { + case BluetoothProfile.STATE_CONNECTING: + mProfilesConnecting++; + if (prevState == BluetoothAdapter.STATE_DISCONNECTING) mProfilesDisconnecting--; + if (mProfilesConnected > 0 || mProfilesConnecting > 1) return false; + + break; + case BluetoothProfile.STATE_CONNECTED: + if (prevState == BluetoothAdapter.STATE_CONNECTING) mProfilesConnecting--; + if (prevState == BluetoothAdapter.STATE_DISCONNECTING) mProfilesDisconnecting--; + + mProfilesConnected++; + + if (mProfilesConnected > 1) return false; + break; + case BluetoothProfile.STATE_DISCONNECTING: + mProfilesDisconnecting++; + mProfilesConnected--; + + if (mProfilesConnected > 0 || mProfilesDisconnecting > 1) return false; + + break; + case BluetoothProfile.STATE_DISCONNECTED: + if (prevState == BluetoothAdapter.STATE_CONNECTING) mProfilesConnecting--; + if (prevState == BluetoothAdapter.STATE_DISCONNECTING) mProfilesDisconnecting--; + + if (prevState == BluetoothAdapter.STATE_CONNECTED) { + mProfilesConnected--; + } + + if (mProfilesConnected > 0 || mProfilesConnecting > 0) return false; + break; + } + return true; + } + private static void log(String msg) { Log.d(TAG, msg); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 0456463..87e03cf 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -7579,6 +7579,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * the auto scaling to true. Doing so, however, will generate a bitmap of a different * size than the view. This implies that your application must be able to handle this * size.</p> + * + * <p>You should avoid calling this method when hardware acceleration is enabled. If + * you do not need the drawing cache bitmap, calling this method will increase memory + * usage and cause the view to be rendered in software once, thus negatively impacting + * performance.</p> * * @see #getDrawingCache() * @see #destroyDrawingCache() @@ -7699,7 +7704,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility canvas.translate(-mScrollX, -mScrollY); mPrivateFlags |= DRAWN; - mPrivateFlags |= DRAWING_CACHE_VALID; + if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated) { + mPrivateFlags |= DRAWING_CACHE_VALID; + } // Fast path for layouts with no backgrounds if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 7b2703b..b881bdd 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -1867,12 +1867,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if ((mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE) { final int count = mChildrenCount; final View[] children = mChildren; + final boolean buildCache = !isHardwareAccelerated(); for (int i = 0; i < count; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { child.setDrawingCacheEnabled(true); - child.buildDrawingCache(true); + if (buildCache) { + child.buildDrawingCache(true); + } } } @@ -1933,6 +1936,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) { final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE; + final boolean buildCache = !isHardwareAccelerated(); for (int i = 0; i < count; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { @@ -1941,7 +1945,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager bindLayoutAnimation(child); if (cache) { child.setDrawingCacheEnabled(true); - child.buildDrawingCache(true); + if (buildCache) { + child.buildDrawingCache(true); + } } } } @@ -2205,8 +2211,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (!canvas.isHardwareAccelerated()) { cache = child.getDrawingCache(true); } else { - // TODO: bring back - // displayList = child.getDisplayList(); + displayList = child.getDisplayList(); } } diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index 2e368b8..06261bb 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -561,6 +561,11 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn scheduleTraversals(); } } + + void invalidate() { + mDirty.set(0, 0, mWidth, mHeight); + scheduleTraversals(); + } public ViewParent getParent() { return null; diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 8e355d6..7cb6291 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -33,6 +33,7 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewRoot; +import android.view.inputmethod.InputMethodSubtype; import com.android.internal.os.HandlerCaller; import com.android.internal.view.IInputConnectionWrapper; @@ -1411,6 +1412,17 @@ public final class InputMethodManager { } } + public InputMethodSubtype getCurrentInputMethodSubtype() { + synchronized (mH) { + try { + return mService.getCurrentInputMethodSubtype(); + } catch (RemoteException e) { + Log.w(TAG, "IME died: " + mCurId, e); + return null; + } + } + } + void doDump(FileDescriptor fd, PrintWriter fout, String[] args) { final Printer p = new PrintWriterPrinter(fout); p.println("Input method client state for " + this + ":"); diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java index 6e588b9..2087664 100644 --- a/core/java/android/webkit/BrowserFrame.java +++ b/core/java/android/webkit/BrowserFrame.java @@ -74,8 +74,6 @@ class BrowserFrame extends Handler { // queue has been cleared,they are ignored. private boolean mBlockMessages = false; private int mOrientation = -1; - private static String sDatabaseDirectory; - private static String sCacheDirectory; // Is this frame the main frame? private boolean mIsMainFrame; @@ -229,13 +227,6 @@ class BrowserFrame extends Handler { AssetManager am = context.getAssets(); nativeCreateFrame(w, am, proxy.getBackForwardList()); - if (sDatabaseDirectory == null) { - sDatabaseDirectory = appContext.getDatabasePath("dummy").getParent(); - } - if (sCacheDirectory == null) { - sCacheDirectory = appContext.getCacheDir().getAbsolutePath(); - } - if (DebugFlags.BROWSER_FRAME) { Log.v(LOGTAG, "BrowserFrame constructor: this=" + this); } @@ -658,22 +649,6 @@ class BrowserFrame extends Handler { } /** - * Called by JNI. Gets the application's database directory, excluding the trailing slash. - * @return String The application's database directory - */ - private static String getDatabaseDirectory() { - return sDatabaseDirectory; - } - - /** - * Called by JNI. Gets the application's cache directory, excluding the trailing slash. - * @return String The application's cache directory - */ - private static String getCacheDirectory() { - return sCacheDirectory; - } - - /** * Called by JNI. * Read from an InputStream into a supplied byte[] * This method catches any exceptions so they don't crash the JVM. @@ -752,6 +727,14 @@ class BrowserFrame extends Handler { } } else if (type == CONTENT) { try { + // Strip off mimetype, for compatibility with ContentLoader.java + // If we don't do this, we can fail to load Gmail attachments, + // because the URL being loaded doesn't exactly match the URL we + // have permission to read. + int mimeIndex = url.lastIndexOf('?'); + if (mimeIndex != -1) { + url = url.substring(0, mimeIndex); + } Uri uri = Uri.parse(url); return mContext.getContentResolver().openInputStream(uri); } catch (Exception e) { diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index 3010178..dcac243 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -273,6 +273,11 @@ public final class CookieManager { * @param accept TRUE if accept cookie */ public synchronized void setAcceptCookie(boolean accept) { + if (useChromiumHttpStack()) { + nativeSetAcceptCookie(accept); + return; + } + mAcceptCookie = accept; } @@ -281,6 +286,10 @@ public final class CookieManager { * @return TRUE if accept cookie */ public synchronized boolean acceptCookie() { + if (useChromiumHttpStack()) { + return nativeAcceptCookie(); + } + return mAcceptCookie; } @@ -418,6 +427,10 @@ public final class CookieManager { * @return The cookies in the format of NAME=VALUE [; NAME=VALUE] */ public String getCookie(String url) { + if (useChromiumHttpStack()) { + return nativeGetCookie(url); + } + WebAddress uri; try { uri = new WebAddress(url); @@ -1035,5 +1048,8 @@ public final class CookieManager { // Native functions private static native boolean nativeUseChromiumHttpStack(); + private static native boolean nativeAcceptCookie(); + private static native String nativeGetCookie(String url); private static native void nativeRemoveAllCookie(); + private static native void nativeSetAcceptCookie(boolean accept); } diff --git a/core/java/android/webkit/CookieSyncManager.java b/core/java/android/webkit/CookieSyncManager.java index abe9178..8b76a3b 100644 --- a/core/java/android/webkit/CookieSyncManager.java +++ b/core/java/android/webkit/CookieSyncManager.java @@ -65,6 +65,11 @@ public final class CookieSyncManager extends WebSyncManager { // time when last update happened private long mLastUpdate; + // Used by the Chromium HTTP stack. Everything else in this class is used only by the Android + // Java HTTP stack. + private static String sDatabaseDirectory; + private static String sCacheDirectory; + private CookieSyncManager(Context context) { super(context, "CookieSyncManager"); } @@ -77,11 +82,7 @@ public final class CookieSyncManager extends WebSyncManager { * @return CookieSyncManager */ public static synchronized CookieSyncManager getInstance() { - if (sRef == null) { - throw new IllegalStateException( - "CookieSyncManager::createInstance() needs to be called " - + "before CookieSyncManager::getInstance()"); - } + checkInstanceIsCreated(); return sRef; } @@ -92,8 +93,11 @@ public final class CookieSyncManager extends WebSyncManager { */ public static synchronized CookieSyncManager createInstance( Context context) { + Context appContext = context.getApplicationContext(); if (sRef == null) { - sRef = new CookieSyncManager(context.getApplicationContext()); + sRef = new CookieSyncManager(appContext); + sDatabaseDirectory = appContext.getDatabasePath("dummy").getParent(); + sCacheDirectory = appContext.getCacheDir().getAbsolutePath(); } return sRef; } @@ -210,4 +214,30 @@ public final class CookieSyncManager extends WebSyncManager { } } } + + private static void checkInstanceIsCreated() { + if (sRef == null) { + throw new IllegalStateException( + "CookieSyncManager::createInstance() needs to be called " + + "before CookieSyncManager::getInstance()"); + } + } + + /** + * Called by JNI. Gets the application's database directory, excluding the trailing slash. + * @return String The application's database directory + */ + private static synchronized String getDatabaseDirectory() { + checkInstanceIsCreated(); + return sDatabaseDirectory; + } + + /** + * Called by JNI. Gets the application's cache directory, excluding the trailing slash. + * @return String The application's cache directory + */ + private static synchronized String getCacheDirectory() { + checkInstanceIsCreated(); + return sCacheDirectory; + } } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index c095199..b668340 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -2471,7 +2471,7 @@ public class WebView extends AbsoluteLayout int viewWidth = getViewWidth(); int newWidth = Math.round(viewWidth * mZoomManager.getInvScale()); - int newHeight = Math.round(getViewHeight() * mZoomManager.getInvScale()); + int newHeight = Math.round((getViewHeightWithTitle() - getTitleHeight()) * mZoomManager.getInvScale()); /* * Because the native side may have already done a layout before the * View system was able to measure us, we have to send a height of 0 to @@ -6047,7 +6047,7 @@ public class WebView extends AbsoluteLayout } private void doMotionUp(int contentX, int contentY) { - if (mLogEvent && nativeMotionUp(contentX, contentY, mNavSlop)) { + if (nativeMotionUp(contentX, contentY, mNavSlop) && mLogEvent) { EventLog.writeEvent(EventLogTags.BROWSER_SNAP_CENTER); } if (nativeHasCursorNode() && !nativeCursorIsTextInput()) { @@ -6447,7 +6447,7 @@ public class WebView extends AbsoluteLayout mUserScroll = false; final WebViewCore.DrawData draw = (WebViewCore.DrawData) msg.obj; setBaseLayer(draw.mBaseLayer, draw.mInvalRegion.getBounds()); - final Point viewSize = draw.mViewPoint; + final Point viewSize = draw.mViewSize; WebViewCore.ViewState viewState = draw.mViewState; boolean isPictureAfterFirstLayout = viewState != null; if (isPictureAfterFirstLayout) { @@ -6470,8 +6470,8 @@ public class WebView extends AbsoluteLayout // received in the fixed dimension. final boolean updateLayout = viewSize.x == mLastWidthSent && viewSize.y == mLastHeightSent; - recordNewContentSize(draw.mWidthHeight.x, - draw.mWidthHeight.y, updateLayout); + recordNewContentSize(draw.mContentSize.x, + draw.mContentSize.y, updateLayout); if (DebugFlags.WEB_VIEW) { Rect b = draw.mInvalRegion.getBounds(); Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" + diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 1c8e2cd..9a873b6 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -260,9 +260,11 @@ final class WebViewCore { * Given mimeType, check whether it's supported in Android media framework. * mimeType could be such as "audio/ogg" and "video/mp4". */ - /* package */ static boolean supportsMimeType(String mimeType) { - return MediaFile.getFileTypeForMimeType(mimeType) > 0; + /* package */ static boolean isSupportedMediaMimeType(String mimeType) { + int fileType = MediaFile.getFileTypeForMimeType(mimeType); + return MediaFile.isAudioFileType(fileType) || MediaFile.isVideoFileType(fileType); } + /** * Add an error message to the client's console. * @param message The message to add @@ -1824,12 +1826,13 @@ final class WebViewCore { DrawData() { mBaseLayer = 0; mInvalRegion = new Region(); - mWidthHeight = new Point(); + mContentSize = new Point(); } int mBaseLayer; Region mInvalRegion; - Point mViewPoint; - Point mWidthHeight; + // view size that was used by webkit during the most recent layout + Point mViewSize; + Point mContentSize; int mMinPrefWidth; // only non-null if it is for the first picture set after the first layout ViewState mViewState; @@ -1840,16 +1843,14 @@ final class WebViewCore { mDrawIsScheduled = false; DrawData draw = new DrawData(); if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw start"); - draw.mBaseLayer = nativeRecordContent(draw.mInvalRegion, draw.mWidthHeight); + draw.mBaseLayer = nativeRecordContent(draw.mInvalRegion, draw.mContentSize); if (draw.mBaseLayer == 0) { if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort"); return; } if (mWebView != null) { - // Send the native view size that was used during the most recent - // layout. draw.mFocusSizeChanged = nativeFocusBoundsChanged(); - draw.mViewPoint = new Point(mCurrentViewWidth, mCurrentViewHeight); + draw.mViewSize = new Point(mCurrentViewWidth, mCurrentViewHeight); if (mSettings.getUseWideViewPort()) { draw.mMinPrefWidth = Math.max( mViewportWidth == -1 ? WebView.DEFAULT_VIEWPORT_WIDTH diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java index 91c0fb2..86dfd1f 100644 --- a/core/java/android/webkit/ZoomManager.java +++ b/core/java/android/webkit/ZoomManager.java @@ -793,9 +793,9 @@ class ZoomManager { // bound to match the default scale for mobile sites. setZoomOverviewWidth(Math.min(WebView.sMaxViewportWidth, Math.max((int) (viewWidth * mInvDefaultScale), - Math.max(drawData.mMinPrefWidth, drawData.mViewPoint.x)))); + Math.max(drawData.mMinPrefWidth, drawData.mViewSize.x)))); } else { - final int contentWidth = drawData.mWidthHeight.x; + final int contentWidth = drawData.mContentSize.x; setZoomOverviewWidth(Math.min(WebView.sMaxViewportWidth, contentWidth)); } } @@ -826,16 +826,16 @@ class ZoomManager { assert mWebView.getSettings() != null; WebViewCore.ViewState viewState = drawData.mViewState; - final Point viewSize = drawData.mViewPoint; + final Point viewSize = drawData.mViewSize; updateZoomRange(viewState, viewSize.x, drawData.mMinPrefWidth); if (mWebView.getSettings().getUseWideViewPort() && mWebView.getSettings().getUseFixedViewport()) { - final int contentWidth = drawData.mWidthHeight.x; + final int contentWidth = drawData.mContentSize.x; setZoomOverviewWidth(Math.min(WebView.sMaxViewportWidth, contentWidth)); } if (!mWebView.drawHistory()) { - final float scale; + float scale; final boolean reflowText; WebSettings settings = mWebView.getSettings(); @@ -847,12 +847,13 @@ class ZoomManager { scale = viewState.mViewScale; reflowText = false; } else { + scale = getZoomOverviewScale(); if (settings.getUseWideViewPort() - && (settings.getLoadWithOverviewMode() || settings.getUseFixedViewport())) { + && settings.getLoadWithOverviewMode()) { mInitialZoomOverview = true; - scale = (float) mWebView.getViewWidth() / mZoomOverviewWidth; } else { - scale = viewState.mTextWrapScale; + scale = Math.max(viewState.mTextWrapScale, scale); + mInitialZoomOverview = !exceedsMinScaleIncrement(scale, getZoomOverviewScale()); } reflowText = exceedsMinScaleIncrement(mTextWrapScale, scale); } diff --git a/core/java/android/widget/SlidingDrawer.java b/core/java/android/widget/SlidingDrawer.java index 11d72de..bdeb5c2 100644 --- a/core/java/android/widget/SlidingDrawer.java +++ b/core/java/android/widget/SlidingDrawer.java @@ -652,7 +652,7 @@ public class SlidingDrawer extends ViewGroup { // Try only once... we should really loop but it's not a big deal // if the draw was cancelled, it will only be temporary anyway content.getViewTreeObserver().dispatchOnPreDraw(); - content.buildDrawingCache(); + if (!content.isHardwareAccelerated()) content.buildDrawingCache(); content.setVisibility(View.GONE); } diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java index 0e92eeb..e6d5984 100644 --- a/core/java/android/widget/StackView.java +++ b/core/java/android/widget/StackView.java @@ -114,6 +114,7 @@ public class StackView extends AdapterViewAnimator { private int mTouchSlop; private int mMaximumVelocity; private VelocityTracker mVelocityTracker; + private boolean mTransitionIsSetup = false; private static HolographicHelper sHolographicHelper; private ImageView mHighlight; @@ -225,6 +226,48 @@ public class StackView extends AdapterViewAnimator { } } + private void setupStackSlider(View v, int mode) { + mStackSlider.setMode(mode); + if (v != null) { + mHighlight.setImageBitmap(sHolographicHelper.createOutline(v)); + mHighlight.setRotation(v.getRotation()); + mHighlight.setTranslationY(v.getTranslationY()); + mHighlight.bringToFront(); + v.bringToFront(); + mStackSlider.setView(v); + + v.setVisibility(VISIBLE); + } + } + + @Override + @android.view.RemotableViewMethod + public void showNext() { + if (!mTransitionIsSetup) { + View v = getViewAtRelativeIndex(1); + if (v != null) { + setupStackSlider(v, StackSlider.NORMAL_MODE); + mStackSlider.setYProgress(0); + mStackSlider.setXProgress(0); + } + } + super.showNext(); + } + + @Override + @android.view.RemotableViewMethod + public void showPrevious() { + if (!mTransitionIsSetup) { + View v = getViewAtRelativeIndex(0); + if (v != null) { + setupStackSlider(v, StackSlider.NORMAL_MODE); + mStackSlider.setYProgress(1); + mStackSlider.setXProgress(0); + } + } + super.showPrevious(); + } + private void transformViewAtIndex(int index, View view) { float maxPerpectiveShift = mMeasuredHeight * PERSPECTIVE_SHIFT_FACTOR; @@ -256,11 +299,12 @@ public class StackView extends AdapterViewAnimator { super.showOnly(childIndex, animate, onLayout); // Here we need to make sure that the z-order of the children is correct - for (int i = mCurrentWindowEnd; i >= mCurrentWindowStart; i--) { + for (int i = mCurrentWindowEnd; i >= mCurrentWindowStart; i--) { int index = modulo(i, getWindowSize()); View v = mViewsMap.get(index).view; if (v != null) v.bringToFront(); } + mTransitionIsSetup = false; } private void updateChildTransforms() { @@ -364,29 +408,24 @@ public class StackView extends AdapterViewAnimator { activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 1 : 0; } + int stackMode; if (mLoopViews) { - mStackSlider.setMode(StackSlider.NORMAL_MODE); + stackMode = StackSlider.NORMAL_MODE; } else if (mCurrentWindowStartUnbounded + activeIndex == -1) { activeIndex++; - mStackSlider.setMode(StackSlider.BEGINNING_OF_STACK_MODE); + stackMode = StackSlider.BEGINNING_OF_STACK_MODE; } else if (mCurrentWindowStartUnbounded + activeIndex == mAdapter.getCount() - 1) { - mStackSlider.setMode(StackSlider.END_OF_STACK_MODE); + stackMode = StackSlider.END_OF_STACK_MODE; } else { - mStackSlider.setMode(StackSlider.NORMAL_MODE); + stackMode = StackSlider.NORMAL_MODE; } + mTransitionIsSetup = stackMode == StackSlider.NORMAL_MODE; + View v = getViewAtRelativeIndex(activeIndex); if (v == null) return; - mHighlight.setImageBitmap(sHolographicHelper.createOutline(v)); - mHighlight.setRotation(v.getRotation()); - mHighlight.setTranslationY(v.getTranslationY()); - mHighlight.bringToFront(); - v.bringToFront(); - mStackSlider.setView(v); - - if (swipeGestureType == GESTURE_SLIDE_DOWN) - v.setVisibility(VISIBLE); + setupStackSlider(v, stackMode); // We only register this gesture if we've made it this far without a problem mSwipeGestureType = swipeGestureType; diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java index ff31dec..2949208 100644 --- a/core/java/android/widget/TabHost.java +++ b/core/java/android/widget/TabHost.java @@ -21,6 +21,7 @@ import com.android.internal.R; import android.app.LocalActivityManager; import android.content.Context; import android.content.Intent; +import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; @@ -63,6 +64,8 @@ public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchMode private OnTabChangeListener mOnTabChangeListener; private OnKeyListener mTabKeyListener; + private int mTabLayoutId; + public TabHost(Context context) { super(context); initTabHost(); @@ -70,6 +73,18 @@ public class TabHost extends FrameLayout implements ViewTreeObserver.OnTouchMode public TabHost(Context context, AttributeSet attrs) { super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.TabWidget, + com.android.internal.R.attr.tabWidgetStyle, 0); + + mTabLayoutId = a.getResourceId(R.styleable.TabWidget_tabLayout, 0); + if (mTabLayoutId == 0) { + throw new IllegalArgumentException("Invalid TabWidget tabLayout id"); + } + + a.recycle(); + initTabHost(); } @@ -214,6 +229,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); if (tabSpec.mIndicatorStrategy instanceof ViewIndicatorStrategy) { mTabWidget.setStripEnabled(false); } + mTabWidget.addView(tabIndicator); mTabSpecs.add(tabSpec); @@ -513,7 +529,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); final Context context = getContext(); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View tabIndicator = inflater.inflate(R.layout.tab_indicator, + View tabIndicator = inflater.inflate(mTabLayoutId, mTabWidget, // tab widget is the parent false); // no inflate params @@ -525,7 +541,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4); tv.setTextColor(context.getResources().getColorStateList(R.color.tab_indicator_text_v4)); } - + return tabIndicator; } } @@ -547,7 +563,7 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); final Context context = getContext(); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View tabIndicator = inflater.inflate(R.layout.tab_indicator, + View tabIndicator = inflater.inflate(mTabLayoutId, mTabWidget, // tab widget is the parent false); // no inflate params @@ -555,14 +571,17 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1"); tv.setText(mLabel); final ImageView iconView = (ImageView) tabIndicator.findViewById(R.id.icon); - iconView.setImageDrawable(mIcon); + if (mIcon != null) { + iconView.setImageDrawable(mIcon); + iconView.setVisibility(VISIBLE); + } if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) { // Donut apps get old color scheme tabIndicator.setBackgroundResource(R.drawable.tab_indicator_v4); tv.setTextColor(context.getResources().getColorStateList(R.color.tab_indicator_text_v4)); } - + return tabIndicator; } } diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java index 0469e7b..36adacd 100644 --- a/core/java/android/widget/TabWidget.java +++ b/core/java/android/widget/TabWidget.java @@ -120,7 +120,9 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { final Context context = mContext; final Resources resources = context.getResources(); - + + // Tests the target Sdk version, as set in the Manifest. Could not be set using styles.xml + // in a values-v? directory which targets the current platform Sdk version instead. if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) { // Donut apps get old color scheme if (mLeftStrip == null) { @@ -131,16 +133,6 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { mRightStrip = resources.getDrawable( com.android.internal.R.drawable.tab_bottom_right_v4); } - } else { - // Use modern color scheme for Eclair and beyond - if (mLeftStrip == null) { - mLeftStrip = resources.getDrawable( - com.android.internal.R.drawable.tab_bottom_left); - } - if (mRightStrip == null) { - mRightStrip = resources.getDrawable( - com.android.internal.R.drawable.tab_bottom_right); - } } // Deal with focus, as we don't want the focus to go by default diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 09563fc..257dbf0 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -6903,14 +6903,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public boolean onTouchEvent(MotionEvent event) { final int action = event.getActionMasked(); - if (action == MotionEvent.ACTION_DOWN) { - if (mInsertionPointCursorController != null) { - mInsertionPointCursorController.onTouchEvent(event); - } - if (mSelectionModifierCursorController != null) { - mSelectionModifierCursorController.onTouchEvent(event); - } + if (mInsertionPointCursorController != null) { + mInsertionPointCursorController.onTouchEvent(event); + } + if (mSelectionModifierCursorController != null) { + mSelectionModifierCursorController.onTouchEvent(event); + } + if (action == MotionEvent.ACTION_DOWN) { // Reset this state; it will be re-set if super.onTouchEvent // causes focus to move to the view. mTouchFocusSelected = false; @@ -6931,13 +6931,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() && mText instanceof Spannable && mLayout != null) { - if (mInsertionPointCursorController != null) { - mInsertionPointCursorController.onTouchEvent(event); - } - if (mSelectionModifierCursorController != null) { - mSelectionModifierCursorController.onTouchEvent(event); - } - boolean handled = false; // Save previous selection, in case this event is used to show the IME. @@ -6946,7 +6939,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final int oldScrollX = mScrollX; final int oldScrollY = mScrollY; - + if (mMovement != null) { handled |= mMovement.onTouchEvent(this, (Spannable) mText, event); } @@ -7142,6 +7135,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case Gravity.RIGHT: return 0.0f; case Gravity.CENTER_HORIZONTAL: + case Gravity.FILL_HORIZONTAL: return (mLayout.getLineWidth(0) - ((mRight - mLeft) - getCompoundPaddingLeft() - getCompoundPaddingRight())) / getHorizontalFadingEdgeLength(); @@ -7980,7 +7974,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } mDrawable = mSelectHandleLeft; handleWidth = mDrawable.getIntrinsicWidth(); - mHotspotX = handleWidth / 4 * 3; + mHotspotX = (handleWidth * 3) / 4; break; } @@ -8263,6 +8257,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Whether selection anchors are active private boolean mIsShowing; + // Double tap detection + private long mPreviousTapUpTime = 0; + private int mPreviousTapPositionX; + private int mPreviousTapPositionY; + private static final int DELAY_BEFORE_FADE_OUT = 4100; private final Runnable mHider = new Runnable() { @@ -8368,6 +8367,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Remember finger down position, to be able to start selection from there mMinTouchOffset = mMaxTouchOffset = getOffset(x, y); + // Double tap detection + long duration = SystemClock.uptimeMillis() - mPreviousTapUpTime; + if (duration <= ViewConfiguration.getDoubleTapTimeout()) { + final int deltaX = x - mPreviousTapPositionX; + final int deltaY = y - mPreviousTapPositionY; + final int distanceSquared = deltaX * deltaX + deltaY * deltaY; + final int doubleTapSlop = + ViewConfiguration.get(getContext()).getScaledDoubleTapSlop(); + final int slopSquared = doubleTapSlop * doubleTapSlop; + if (distanceSquared < slopSquared) { + startSelectionActionMode(); + // Hacky: onTapUpEvent will open a context menu with cut/copy + // Prevent this by hiding handles which will be revived instead. + hide(); + } + } + + mPreviousTapPositionX = x; + mPreviousTapPositionY = y; + break; case MotionEvent.ACTION_POINTER_DOWN: @@ -8379,6 +8398,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener updateMinAndMaxOffsets(event); } break; + + case MotionEvent.ACTION_UP: + mPreviousTapUpTime = SystemClock.uptimeMillis(); + break; } } return false; diff --git a/core/java/com/android/internal/nfc/LlcpServiceSocket.java b/core/java/com/android/internal/nfc/LlcpServiceSocket.java index 4607527..318982b 100644 --- a/core/java/com/android/internal/nfc/LlcpServiceSocket.java +++ b/core/java/com/android/internal/nfc/LlcpServiceSocket.java @@ -78,8 +78,9 @@ public class LlcpServiceSocket { * @param handle * The handle returned by the NFC service and used to identify * the socket in subsequent calls. + * @hide */ - LlcpServiceSocket(ILlcpServiceSocket service, ILlcpSocket socketService, int handle) { + public LlcpServiceSocket(ILlcpServiceSocket service, ILlcpSocket socketService, int handle) { this.mService = service; this.mHandle = handle; this.mLlcpSocketService = socketService; diff --git a/core/java/com/android/internal/nfc/LlcpSocket.java b/core/java/com/android/internal/nfc/LlcpSocket.java index ae74002..b1b1320 100644 --- a/core/java/com/android/internal/nfc/LlcpSocket.java +++ b/core/java/com/android/internal/nfc/LlcpSocket.java @@ -78,8 +78,9 @@ public class LlcpSocket { * @param handle * The handle returned by the NFC service and used to identify * the socket in subsequent calls. + * @hide */ - LlcpSocket(ILlcpSocket service, int handle) { + public LlcpSocket(ILlcpSocket service, int handle) { this.mService = service; this.mHandle = handle; } diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index da0c5a2..c473fd2 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -681,19 +681,6 @@ class ZygoteConnection { throws ZygoteInit.MethodAndArgsCaller { /* - * First, set the capabilities if necessary - */ - - if (parsedArgs.uid != 0) { - try { - ZygoteInit.setCapabilities(parsedArgs.permittedCapabilities, - parsedArgs.effectiveCapabilities); - } catch (IOException ex) { - Log.e(TAG, "Error setting capabilities", ex); - } - } - - /* * Close the socket, unless we're in "peer wait" mode, in which * case it's used to track the liveness of this process. */ diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 1d54c61..848bf9e 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -492,18 +492,6 @@ public class ZygoteInit { private static void handleSystemServerProcess( ZygoteConnection.Arguments parsedArgs) throws ZygoteInit.MethodAndArgsCaller { - /* - * First, set the capabilities if necessary - */ - - if (parsedArgs.uid != 0) { - try { - setCapabilities(parsedArgs.permittedCapabilities, - parsedArgs.effectiveCapabilities); - } catch (IOException ex) { - Log.e(TAG, "Error setting capabilities", ex); - } - } closeServerSocket(); @@ -552,7 +540,9 @@ public class ZygoteInit { /* Request to fork the system server process */ pid = Zygote.forkSystemServer( parsedArgs.uid, parsedArgs.gid, - parsedArgs.gids, debugFlags, null); + parsedArgs.gids, debugFlags, null, + parsedArgs.permittedCapabilities, + parsedArgs.effectiveCapabilities); } catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index bffec1d..49ae2bc 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -18,6 +18,7 @@ package com.android.internal.view; import android.os.ResultReceiver; import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.EditorInfo; import com.android.internal.view.InputBindResult; import com.android.internal.view.IInputContext; @@ -54,6 +55,7 @@ interface IInputMethodManager { void hideMySoftInput(in IBinder token, int flags); void showMySoftInput(in IBinder token, int flags); void updateStatusIcon(in IBinder token, String packageName, int iconId); + InputMethodSubtype getCurrentInputMethodSubtype(); boolean setInputMethodEnabled(String id, boolean enabled); } diff --git a/core/java/com/android/internal/view/InputMethodAndSubtypeEnabler.java b/core/java/com/android/internal/view/InputMethodAndSubtypeEnabler.java deleted file mode 100644 index 200d49f..0000000 --- a/core/java/com/android/internal/view/InputMethodAndSubtypeEnabler.java +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.view; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.res.Configuration; -import android.os.Bundle; -import android.preference.CheckBoxPreference; -import android.preference.Preference; -import android.preference.PreferenceActivity; -import android.preference.PreferenceCategory; -import android.preference.PreferenceScreen; -import android.provider.Settings; -import android.text.TextUtils; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.InputMethodSubtype; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; - -public class InputMethodAndSubtypeEnabler extends PreferenceActivity { - - private boolean mHaveHardKeyboard; - - private List<InputMethodInfo> mInputMethodProperties; - - private final TextUtils.SimpleStringSplitter mStringColonSplitter - = new TextUtils.SimpleStringSplitter(':'); - - private String mLastInputMethodId; - private String mLastTickedInputMethodId; - - private AlertDialog mDialog = null; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - Configuration config = getResources().getConfiguration(); - mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY); - onCreateIMM(); - setPreferenceScreen(createPreferenceHierarchy()); - } - - @Override - protected void onResume() { - super.onResume(); - loadInputMethodSubtypeList(); - } - - @Override - protected void onPause() { - super.onPause(); - saveInputMethodSubtypeList(); - } - - @Override - public boolean onPreferenceTreeClick( - PreferenceScreen preferenceScreen, Preference preference) { - - if (preference instanceof CheckBoxPreference) { - final CheckBoxPreference chkPref = (CheckBoxPreference) preference; - final String id = chkPref.getKey(); - // TODO: Check subtype or not here - if (chkPref.isChecked()) { - InputMethodInfo selImi = null; - final int N = mInputMethodProperties.size(); - for (int i = 0; i < N; i++) { - InputMethodInfo imi = mInputMethodProperties.get(i); - if (id.equals(imi.getId())) { - selImi = imi; - if (isSystemIme(imi)) { - setSubtypesPreferenceEnabled(id, true); - // This is a built-in IME, so no need to warn. - mLastTickedInputMethodId = id; - return super.onPreferenceTreeClick(preferenceScreen, preference); - } - break; - } - } - if (selImi == null) { - return super.onPreferenceTreeClick(preferenceScreen, preference); - } - chkPref.setChecked(false); - if (mDialog == null) { - mDialog = (new AlertDialog.Builder(this)) - .setTitle(android.R.string.dialog_alert_title) - .setIcon(android.R.drawable.ic_dialog_alert) - .setCancelable(true) - .setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - chkPref.setChecked(true); - setSubtypesPreferenceEnabled(id, true); - mLastTickedInputMethodId = id; - } - - }) - .setNegativeButton(android.R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - } - - }) - .create(); - } else { - if (mDialog.isShowing()) { - mDialog.dismiss(); - } - } - mDialog.setMessage(getResources().getString( - com.android.internal.R.string.ime_enabler_security_warning, - selImi.getServiceInfo().applicationInfo.loadLabel(getPackageManager()))); - mDialog.show(); - } else { - if (id.equals(mLastTickedInputMethodId)) { - mLastTickedInputMethodId = null; - } - setSubtypesPreferenceEnabled(id, false); - } - } - return super.onPreferenceTreeClick(preferenceScreen, preference); - } - - @Override - public void onDestroy() { - super.onDestroy(); - if (mDialog != null) { - mDialog.dismiss(); - mDialog = null; - } - } - - private void onCreateIMM() { - InputMethodManager imm = (InputMethodManager) getSystemService( - Context.INPUT_METHOD_SERVICE); - - // TODO: Change mInputMethodProperties to Map - mInputMethodProperties = imm.getInputMethodList(); - - mLastInputMethodId = Settings.Secure.getString(getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD); - } - - private PreferenceScreen createPreferenceHierarchy() { - // Root - PreferenceScreen root = getPreferenceManager().createPreferenceScreen(this); - - int N = (mInputMethodProperties == null ? 0 : mInputMethodProperties.size()); - // TODO: Use iterator. - for (int i = 0; i < N; ++i) { - PreferenceCategory keyboardSettingsCategory = new PreferenceCategory(this); - root.addPreference(keyboardSettingsCategory); - InputMethodInfo property = mInputMethodProperties.get(i); - String prefKey = property.getId(); - - PackageManager pm = getPackageManager(); - CharSequence label = property.loadLabel(pm); - boolean systemIME = isSystemIme(property); - - keyboardSettingsCategory.setTitle(label); - - // Add a check box. - // Don't show the toggle if it's the only keyboard in the system, or it's a system IME. - if (mHaveHardKeyboard || (N > 1 && !systemIME)) { - CheckBoxPreference chkbxPref = new CheckBoxPreference(this); - chkbxPref.setKey(prefKey); - chkbxPref.setTitle(label); - keyboardSettingsCategory.addPreference(chkbxPref); - } - - ArrayList<InputMethodSubtype> subtypes = property.getSubtypes(); - if (subtypes.size() > 0) { - PreferenceCategory subtypesCategory = new PreferenceCategory(this); - subtypesCategory.setTitle(getResources().getString( - com.android.internal.R.string.ime_enabler_subtype_title, label)); - root.addPreference(subtypesCategory); - for (InputMethodSubtype subtype: subtypes) { - CharSequence subtypeLabel; - int nameResId = subtype.getNameResId(); - if (nameResId != 0) { - subtypeLabel = pm.getText(property.getPackageName(), nameResId, - property.getServiceInfo().applicationInfo); - } else { - int modeResId = subtype.getModeResId(); - CharSequence language = subtype.getLocale(); - CharSequence mode = modeResId == 0 ? null - : pm.getText(property.getPackageName(), modeResId, - property.getServiceInfo().applicationInfo); - // TODO: Use more friendly Title and UI - subtypeLabel = (mode == null ? "" : mode) + "," - + (language == null ? "" : language); - } - CheckBoxPreference chkbxPref = new CheckBoxPreference(this); - chkbxPref.setKey(prefKey + subtype.hashCode()); - chkbxPref.setTitle(subtypeLabel); - chkbxPref.setSummary(label); - subtypesCategory.addPreference(chkbxPref); - } - } - } - return root; - } - - private void loadInputMethodSubtypeList() { - final HashSet<String> enabled = new HashSet<String>(); - String enabledStr = Settings.Secure.getString(getContentResolver(), - Settings.Secure.ENABLED_INPUT_METHODS); - if (enabledStr != null) { - final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; - splitter.setString(enabledStr); - while (splitter.hasNext()) { - enabled.add(splitter.next()); - } - } - - // Update the statuses of the Check Boxes. - int N = mInputMethodProperties.size(); - // TODO: Use iterator. - for (int i = 0; i < N; ++i) { - final String id = mInputMethodProperties.get(i).getId(); - CheckBoxPreference pref = (CheckBoxPreference) findPreference( - mInputMethodProperties.get(i).getId()); - if (pref != null) { - boolean isEnabled = enabled.contains(id); - pref.setChecked(isEnabled); - setSubtypesPreferenceEnabled(id, isEnabled); - } - } - mLastTickedInputMethodId = null; - } - - private void saveInputMethodSubtypeList() { - StringBuilder builder = new StringBuilder(); - StringBuilder disabledSysImes = new StringBuilder(); - - int firstEnabled = -1; - int N = mInputMethodProperties.size(); - for (int i = 0; i < N; ++i) { - final InputMethodInfo property = mInputMethodProperties.get(i); - final String id = property.getId(); - CheckBoxPreference pref = (CheckBoxPreference) findPreference(id); - boolean currentInputMethod = id.equals(mLastInputMethodId); - boolean systemIme = isSystemIme(property); - // TODO: Append subtypes by using the separator ";" - if (((N == 1 || systemIme) && !mHaveHardKeyboard) - || (pref != null && pref.isChecked())) { - if (builder.length() > 0) builder.append(':'); - builder.append(id); - if (firstEnabled < 0) { - firstEnabled = i; - } - } else if (currentInputMethod) { - mLastInputMethodId = mLastTickedInputMethodId; - } - // If it's a disabled system ime, add it to the disabled list so that it - // doesn't get enabled automatically on any changes to the package list - if (pref != null && !pref.isChecked() && systemIme && mHaveHardKeyboard) { - if (disabledSysImes.length() > 0) disabledSysImes.append(":"); - disabledSysImes.append(id); - } - } - - // If the last input method is unset, set it as the first enabled one. - if (TextUtils.isEmpty(mLastInputMethodId)) { - if (firstEnabled >= 0) { - mLastInputMethodId = mInputMethodProperties.get(firstEnabled).getId(); - } else { - mLastInputMethodId = null; - } - } - - Settings.Secure.putString(getContentResolver(), - Settings.Secure.ENABLED_INPUT_METHODS, builder.toString()); - Settings.Secure.putString(getContentResolver(), - Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, disabledSysImes.toString()); - Settings.Secure.putString(getContentResolver(), - Settings.Secure.DEFAULT_INPUT_METHOD, - mLastInputMethodId != null ? mLastInputMethodId : ""); - } - - private void setSubtypesPreferenceEnabled(String id, boolean enabled) { - PreferenceScreen preferenceScreen = getPreferenceScreen(); - final int N = mInputMethodProperties.size(); - // TODO: Use iterator. - for (int i = 0; i < N; i++) { - InputMethodInfo imi = mInputMethodProperties.get(i); - if (id.equals(imi.getId())) { - for (InputMethodSubtype subtype: imi.getSubtypes()) { - preferenceScreen.findPreference(id + subtype.hashCode()).setEnabled(enabled); - } - } - } - } - - private boolean isSystemIme(InputMethodInfo property) { - return (property.getServiceInfo().applicationInfo.flags - & ApplicationInfo.FLAG_SYSTEM) != 0; - } -} |
