diff options
208 files changed, 9479 insertions, 6304 deletions
diff --git a/Android.mk b/Android.mk index 15a41969c769..5879bb5b9db5 100644 --- a/Android.mk +++ b/Android.mk @@ -441,6 +441,8 @@ web_docs_sample_code_flags := \ resources/samples/HoneycombGallery "Honeycomb Gallery" \ -samplecode $(sample_dir)/JetBoy \ resources/samples/JetBoy "JetBoy" \ + -samplecode $(sample_dir)/KeyChainDemo \ + resources/samples/KeyChainDemo "KeyChain Demo" \ -samplecode $(sample_dir)/LunarLander \ resources/samples/LunarLander "Lunar Lander" \ -samplecode $(sample_dir)/training/ads-and-ux \ diff --git a/api/current.txt b/api/current.txt index 233938b5fdef..eba8a2c22910 100644 --- a/api/current.txt +++ b/api/current.txt @@ -22490,6 +22490,7 @@ package android.view { field public static final int KEYCODE_DPAD_UP = 19; // 0x13 field public static final int KEYCODE_DVR = 173; // 0xad field public static final int KEYCODE_E = 33; // 0x21 + field public static final int KEYCODE_EISU = 212; // 0xd4 field public static final int KEYCODE_ENDCALL = 6; // 0x6 field public static final int KEYCODE_ENTER = 66; // 0x42 field public static final int KEYCODE_ENVELOPE = 65; // 0x41 @@ -22518,12 +22519,15 @@ package android.view { field public static final int KEYCODE_GUIDE = 172; // 0xac field public static final int KEYCODE_H = 36; // 0x24 field public static final int KEYCODE_HEADSETHOOK = 79; // 0x4f + field public static final int KEYCODE_HENKAN = 214; // 0xd6 field public static final int KEYCODE_HOME = 3; // 0x3 field public static final int KEYCODE_I = 37; // 0x25 field public static final int KEYCODE_INFO = 165; // 0xa5 field public static final int KEYCODE_INSERT = 124; // 0x7c field public static final int KEYCODE_J = 38; // 0x26 field public static final int KEYCODE_K = 39; // 0x27 + field public static final int KEYCODE_KANA = 218; // 0xda + field public static final int KEYCODE_KATAKANA_HIRAGANA = 215; // 0xd7 field public static final int KEYCODE_L = 40; // 0x28 field public static final int KEYCODE_LANGUAGE_SWITCH = 204; // 0xcc field public static final int KEYCODE_LEFT_BRACKET = 71; // 0x47 @@ -22546,6 +22550,7 @@ package android.view { field public static final int KEYCODE_MINUS = 69; // 0x45 field public static final int KEYCODE_MOVE_END = 123; // 0x7b field public static final int KEYCODE_MOVE_HOME = 122; // 0x7a + field public static final int KEYCODE_MUHENKAN = 213; // 0xd5 field public static final int KEYCODE_MUSIC = 209; // 0xd1 field public static final int KEYCODE_MUTE = 91; // 0x5b field public static final int KEYCODE_N = 42; // 0x2a @@ -22588,6 +22593,7 @@ package android.view { field public static final int KEYCODE_Q = 45; // 0x2d field public static final int KEYCODE_R = 46; // 0x2e field public static final int KEYCODE_RIGHT_BRACKET = 72; // 0x48 + field public static final int KEYCODE_RO = 217; // 0xd9 field public static final int KEYCODE_S = 47; // 0x2f field public static final int KEYCODE_SCROLL_LOCK = 116; // 0x74 field public static final int KEYCODE_SEARCH = 84; // 0x54 @@ -22620,7 +22626,9 @@ package android.view { field public static final int KEYCODE_WINDOW = 171; // 0xab field public static final int KEYCODE_X = 52; // 0x34 field public static final int KEYCODE_Y = 53; // 0x35 + field public static final int KEYCODE_YEN = 216; // 0xd8 field public static final int KEYCODE_Z = 54; // 0x36 + field public static final int KEYCODE_ZENKAKU_HANKAKU = 211; // 0xd3 field public static final int KEYCODE_ZOOM_IN = 168; // 0xa8 field public static final int KEYCODE_ZOOM_OUT = 169; // 0xa9 field public static final deprecated int MAX_KEYCODE = 84; // 0x54 @@ -23240,6 +23248,7 @@ package android.view { method public boolean dispatchUnhandledMove(android.view.View, int); method protected void dispatchVisibilityChanged(android.view.View, int); method public void dispatchWindowFocusChanged(boolean); + method public void dispatchWindowSystemUiVisiblityChanged(int); method public void dispatchWindowVisibilityChanged(int); method public void draw(android.graphics.Canvas); method protected void drawableStateChanged(); @@ -23353,6 +23362,7 @@ package android.view { method public int getVisibility(); method public final int getWidth(); method protected int getWindowAttachCount(); + method public int getWindowSystemUiVisibility(); method public android.os.IBinder getWindowToken(); method public int getWindowVisibility(); method public void getWindowVisibleDisplayFrame(android.graphics.Rect); @@ -23456,6 +23466,7 @@ package android.view { method public boolean onTrackballEvent(android.view.MotionEvent); method protected void onVisibilityChanged(android.view.View, int); method public void onWindowFocusChanged(boolean); + method public void onWindowSystemUiVisibilityChanged(int); method protected void onWindowVisibilityChanged(int); method protected boolean overScrollBy(int, int, int, int, int, int, int, int, boolean); method public boolean performClick(); @@ -23473,6 +23484,7 @@ package android.view { method public boolean removeCallbacks(java.lang.Runnable); method public void removeOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener); method public void removeOnLayoutChangeListener(android.view.View.OnLayoutChangeListener); + method public void requestFitSystemWindows(); method public final boolean requestFocus(); method public final boolean requestFocus(int); method public boolean requestFocus(int, android.graphics.Rect); @@ -23671,9 +23683,14 @@ package android.view { field public static final int SOUND_EFFECTS_ENABLED = 134217728; // 0x8000000 field public static final deprecated int STATUS_BAR_HIDDEN = 1; // 0x1 field public static final deprecated int STATUS_BAR_VISIBLE = 0; // 0x0 + field public static final int SYSTEM_UI_FLAG_FULLSCREEN = 4; // 0x4 field public static final int SYSTEM_UI_FLAG_HIDE_NAVIGATION = 2; // 0x2 + field public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 1024; // 0x400 + field public static final int SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = 512; // 0x200 + field public static final int SYSTEM_UI_FLAG_LAYOUT_STABLE = 256; // 0x100 field public static final int SYSTEM_UI_FLAG_LOW_PROFILE = 1; // 0x1 field public static final int SYSTEM_UI_FLAG_VISIBLE = 0; // 0x0 + field public static final int SYSTEM_UI_LAYOUT_FLAGS = 1536; // 0x600 field public static final int TEXT_DIRECTION_ANY_RTL = 2; // 0x2 field protected static int TEXT_DIRECTION_DEFAULT; field public static final int TEXT_DIRECTION_FIRST_STRONG = 1; // 0x1 @@ -24037,6 +24054,7 @@ package android.view { method public abstract void requestChildFocus(android.view.View, android.view.View); method public abstract boolean requestChildRectangleOnScreen(android.view.View, android.graphics.Rect, boolean); method public abstract void requestDisallowInterceptTouchEvent(boolean); + method public abstract void requestFitSystemWindows(); method public abstract void requestLayout(); method public abstract boolean requestSendAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent); method public abstract void requestTransparentRegion(android.view.View); diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java index 24d3a6b3045d..cff16ff61592 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -611,6 +611,10 @@ public abstract class ActionBar { * If the window hosting the ActionBar does not have the feature * {@link Window#FEATURE_ACTION_BAR_OVERLAY} it will resize application * content to fit the new space available. + * + * <p>If you are hiding the ActionBar through + * {@link View#SYSTEM_UI_FLAG_FULLSCREEN View.SYSTEM_UI_FLAG_FULLSCREEN}, + * you should not call this function directly. */ public abstract void show(); @@ -619,6 +623,12 @@ public abstract class ActionBar { * If the window hosting the ActionBar does not have the feature * {@link Window#FEATURE_ACTION_BAR_OVERLAY} it will resize application * content to fit the new space available. + * + * <p>Instead of calling this function directly, you can also cause an + * ActionBar using the overlay feature to hide through + * {@link View#SYSTEM_UI_FLAG_FULLSCREEN View.SYSTEM_UI_FLAG_FULLSCREEN}. + * Hiding the ActionBar through this system UI flag allows you to more + * seamlessly hide it in conjunction with other screen decorations. */ public abstract void hide(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index ab4e73d5879c..98c4e10a2886 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -174,8 +174,10 @@ public final class ActivityThread { static final ThreadLocal<ActivityThread> sThreadLocal = new ThreadLocal<ActivityThread>(); Instrumentation mInstrumentation; String mInstrumentationAppDir = null; + String mInstrumentationAppLibraryDir = null; String mInstrumentationAppPackage = null; String mInstrumentedAppDir = null; + String mInstrumentedAppLibraryDir = null; boolean mSystemThread = false; boolean mJitEnabled = false; @@ -3936,8 +3938,10 @@ public final class ActivityThread { } mInstrumentationAppDir = ii.sourceDir; + mInstrumentationAppLibraryDir = ii.nativeLibraryDir; mInstrumentationAppPackage = ii.packageName; mInstrumentedAppDir = data.info.getAppDir(); + mInstrumentedAppLibraryDir = data.info.getLibDir(); ApplicationInfo instrApp = new ApplicationInfo(); instrApp.packageName = ii.packageName; diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 5340fbb23561..8ab1ed66409d 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -261,6 +261,7 @@ public final class LoadedApk { if (mIncludeCode && !mPackageName.equals("android")) { String zip = mAppDir; + String libraryPath = mLibDir; /* * The following is a bit of a hack to inject @@ -273,15 +274,20 @@ public final class LoadedApk { String instrumentationAppDir = mActivityThread.mInstrumentationAppDir; + String instrumentationAppLibraryDir = + mActivityThread.mInstrumentationAppLibraryDir; String instrumentationAppPackage = mActivityThread.mInstrumentationAppPackage; String instrumentedAppDir = mActivityThread.mInstrumentedAppDir; + String instrumentedAppLibraryDir = + mActivityThread.mInstrumentedAppLibraryDir; String[] instrumentationLibs = null; if (mAppDir.equals(instrumentationAppDir) || mAppDir.equals(instrumentedAppDir)) { zip = instrumentationAppDir + ":" + instrumentedAppDir; + libraryPath = instrumentationAppLibraryDir + ":" + instrumentedAppLibraryDir; if (! instrumentedAppDir.equals(instrumentationAppDir)) { instrumentationLibs = getLibrariesFor(instrumentationAppPackage); @@ -301,7 +307,7 @@ public final class LoadedApk { */ if (ActivityThread.localLOGV) - Slog.v(ActivityThread.TAG, "Class path: " + zip + ", JNI path: " + mLibDir); + Slog.v(ActivityThread.TAG, "Class path: " + zip + ", JNI path: " + libraryPath); // Temporarily disable logging of disk reads on the Looper thread // as this is early and necessary. @@ -309,7 +315,7 @@ public final class LoadedApk { mClassLoader = ApplicationLoaders.getDefault().getClassLoader( - zip, mLibDir, mBaseClassLoader); + zip, libraryPath, mBaseClassLoader); initializeJavaContextClassLoader(); StrictMode.setThreadPolicy(oldPolicy); @@ -442,6 +448,10 @@ public final class LoadedApk { return mAppDir; } + public String getLibDir() { + return mLibDir; + } + public String getResDir() { return mResDir; } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 1356801db229..096af93086b3 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -28,6 +28,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.IntProperty; +import android.util.Log; import android.view.View; import android.widget.ProgressBar; import android.widget.RemoteViews; @@ -808,7 +809,7 @@ public class Notification implements Parcelable public void setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) { RemoteViews contentView = new RemoteViews(context.getPackageName(), - R.layout.status_bar_latest_event_content); + R.layout.notification_template_base); if (this.icon != 0) { contentView.setImageViewResource(R.id.icon, this.icon); } @@ -1439,11 +1440,28 @@ public class Notification implements Parcelable return contentView; } + private RemoteViews applyStandardTemplateWithActions(int layoutId) { + RemoteViews big = applyStandardTemplate(layoutId); + + int N = mActions.size(); + if (N > 0) { + Log.d("Notification", "has actions: " + mContentText); + big.setViewVisibility(R.id.actions, View.VISIBLE); + if (N>3) N=3; + for (int i=0; i<N; i++) { + final RemoteViews button = generateActionButton(mActions.get(i)); + Log.d("Notification", "adding action " + i + ": " + mActions.get(i).title); + big.addView(R.id.actions, button); + } + } + return big; + } + private RemoteViews makeContentView() { if (mContentView != null) { return mContentView; } else { - return applyStandardTemplate(R.layout.status_bar_latest_event_content); // no more special large_icon flavor + return applyStandardTemplate(R.layout.notification_template_base); // no more special large_icon flavor } } @@ -1461,6 +1479,12 @@ public class Notification implements Parcelable } } + private RemoteViews makeBigContentView() { + if (mActions.size() == 0) return null; + + return applyStandardTemplateWithActions(R.layout.notification_template_base); + } + private RemoteViews makeIntruderView(boolean showLabels) { RemoteViews intruderView = new RemoteViews(mContext.getPackageName(), R.layout.notification_intruder_content); @@ -1500,6 +1524,15 @@ public class Notification implements Parcelable return intruderView; } + private RemoteViews generateActionButton(Action action) { + RemoteViews button = new RemoteViews(mContext.getPackageName(), R.layout.notification_action); + button.setTextViewCompoundDrawables(R.id.action0, action.icon, 0, 0, 0); + button.setTextViewText(R.id.action0, action.title); + button.setOnClickPendingIntent(R.id.action0, action.actionIntent); + button.setContentDescription(R.id.action0, action.title); + return button; + } + /** * Combine all of the options that have been set and return a new {@link Notification} * object. @@ -1528,6 +1561,7 @@ public class Notification implements Parcelable if (mCanHasIntruder) { n.intruderView = makeIntruderView(mIntruderActionsShowText); } + n.bigContentView = makeBigContentView(); if (mLedOnMs != 0 && mLedOffMs != 0) { n.flags |= FLAG_SHOW_LIGHTS; } @@ -1583,7 +1617,7 @@ public class Notification implements Parcelable } private RemoteViews makeBigContentView() { - RemoteViews contentView = mBuilder.applyStandardTemplate(R.layout.notification_template_big_picture); + RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(R.layout.notification_template_big_picture); contentView.setImageViewBitmap(R.id.big_picture, mPicture); @@ -1630,7 +1664,7 @@ public class Notification implements Parcelable } private RemoteViews makeBigContentView() { - RemoteViews contentView = mBuilder.applyStandardTemplate(R.layout.status_bar_latest_event_content); + RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(R.layout.notification_template_base); contentView.setTextViewText(R.id.big_text, mBigText); contentView.setViewVisibility(R.id.big_text, View.VISIBLE); diff --git a/core/java/android/nfc/INfcTag.aidl b/core/java/android/nfc/INfcTag.aidl index 102b6af5f64e..2223255ec91a 100644 --- a/core/java/android/nfc/INfcTag.aidl +++ b/core/java/android/nfc/INfcTag.aidl @@ -29,7 +29,6 @@ interface INfcTag int connect(int nativeHandle, int technology); int reconnect(int nativeHandle); int[] getTechList(int nativeHandle); - byte[] getUid(int nativeHandle); boolean isNdef(int nativeHandle); boolean isPresent(int nativeHandle); TransceiveResult transceive(int nativeHandle, in byte[] data, boolean raw); diff --git a/core/java/android/nfc/tech/Ndef.java b/core/java/android/nfc/tech/Ndef.java index b1d53036aaa8..a31cb9caec19 100644 --- a/core/java/android/nfc/tech/Ndef.java +++ b/core/java/android/nfc/tech/Ndef.java @@ -256,6 +256,8 @@ public final class Ndef extends BasicTagTechnology { * not be called from the main application thread. A blocked call will be canceled with * {@link IOException} if {@link #close} is called from another thread. * + * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission. + * * @return the NDEF Message, can be null * @throws TagLostException if the tag leaves the field * @throws IOException if there is an I/O failure, or the operation is canceled diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java index b816b110ac44..4aa7fe2df272 100644 --- a/core/java/android/os/Message.java +++ b/core/java/android/os/Message.java @@ -16,9 +16,6 @@ package android.os; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; import android.util.TimeUtils; /** @@ -368,13 +365,13 @@ public final class Message implements Parcelable { * * Asynchronous messages represent interrupts or events that do not require global ordering * with represent to synchronous messages. Asynchronous messages are not subject to - * the synchronization barriers introduced by {@link MessageQueue#acquireSyncBarrier()}. + * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}. * * @return True if the message is asynchronous. * * @see #setAsynchronous(boolean) - * @see MessageQueue#acquireSyncBarrier() - * @see MessageQueue#releaseSyncBarrier() + * @see MessageQueue#enqueueSyncBarrier(long) + * @see MessageQueue#removeSyncBarrier(int) * * @hide */ @@ -387,13 +384,13 @@ public final class Message implements Parcelable { * * Asynchronous messages represent interrupts or events that do not require global ordering * with represent to synchronous messages. Asynchronous messages are not subject to - * the synchronization barriers introduced by {@link MessageQueue#acquireSyncBarrier()}. + * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}. * * @param async True if the message is asynchronous. * * @see #isAsynchronous() - * @see MessageQueue#acquireSyncBarrier() - * @see MessageQueue#releaseSyncBarrier() + * @see MessageQueue#enqueueSyncBarrier(long) + * @see MessageQueue#removeSyncBarrier(int) * * @hide */ @@ -506,7 +503,7 @@ public final class Message implements Parcelable { Messenger.writeMessengerOrNullToParcel(replyTo, dest); } - private final void readFromParcel(Parcel source) { + private void readFromParcel(Parcel source) { what = source.readInt(); arg1 = source.readInt(); arg2 = source.readInt(); diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java index b70875060b30..f7a7eb8f65f4 100644 --- a/core/java/android/text/SpannableStringBuilder.java +++ b/core/java/android/text/SpannableStringBuilder.java @@ -262,19 +262,8 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable return append(String.valueOf(text)); } - private int change(int start, int end, CharSequence tb, int tbstart, int tbend) { - return change(true, start, end, tb, tbstart, tbend); - } - - private int change(boolean notify, int start, int end, - CharSequence tb, int tbstart, int tbend) { + private void change(int start, int end, CharSequence tb, int tbstart, int tbend) { checkRange("replace", start, end); - int ret = tbend - tbstart; - TextWatcher[] recipients = null; - - if (notify) { - recipients = sendTextWillChange(start, end - start, tbend - tbstart); - } for (int i = mSpanCount - 1; i >= 0; i--) { if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) { @@ -346,51 +335,37 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable } } - // no need for span fixup on pure insertion - if (tbend > tbstart && end - start == 0) { - if (notify) { - sendTextChange(recipients, start, end - start, tbend - tbstart); - sendTextHasChanged(recipients); - } - - return ret; - } - - boolean atend = (mGapStart + mGapLength == mText.length); + if (end > start) { + // no need for span fixup on pure insertion + boolean atEnd = (mGapStart + mGapLength == mText.length); - for (int i = mSpanCount - 1; i >= 0; i--) { - if (mSpanStarts[i] >= start && - mSpanStarts[i] < mGapStart + mGapLength) { - int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT; + for (int i = mSpanCount - 1; i >= 0; i--) { + if (mSpanStarts[i] >= start && + mSpanStarts[i] < mGapStart + mGapLength) { + int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT; - if (flag == POINT || (flag == PARAGRAPH && atend)) + if (flag == POINT || (flag == PARAGRAPH && atEnd)) mSpanStarts[i] = mGapStart + mGapLength; else mSpanStarts[i] = start; - } + } - if (mSpanEnds[i] >= start && - mSpanEnds[i] < mGapStart + mGapLength) { - int flag = (mSpanFlags[i] & END_MASK); + if (mSpanEnds[i] >= start && + mSpanEnds[i] < mGapStart + mGapLength) { + int flag = (mSpanFlags[i] & END_MASK); - if (flag == POINT || (flag == PARAGRAPH && atend)) - mSpanEnds[i] = mGapStart + mGapLength; - else - mSpanEnds[i] = start; - } + if (flag == POINT || (flag == PARAGRAPH && atEnd)) + mSpanEnds[i] = mGapStart + mGapLength; + else + mSpanEnds[i] = start; + } - // remove 0-length SPAN_EXCLUSIVE_EXCLUSIVE - if (mSpanEnds[i] < mSpanStarts[i]) { - removeSpan(i); + // remove 0-length SPAN_EXCLUSIVE_EXCLUSIVE + if (mSpanEnds[i] < mSpanStarts[i]) { + removeSpan(i); + } } } - - if (notify) { - sendTextChange(recipients, start, end - start, tbend - tbstart); - sendTextHasChanged(recipients); - } - - return ret; } private void removeSpan(int i) { @@ -425,8 +400,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable CharSequence tb, int tbstart, int tbend) { int filtercount = mFilters.length; for (int i = 0; i < filtercount; i++) { - CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, - this, start, end); + CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end); if (repl != null) { tb = repl; @@ -435,11 +409,17 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable } } - if (end == start && tbstart == tbend) { + final int origLen = end - start; + final int newLen = tbend - tbstart; + + if (origLen == 0 && newLen == 0) { return this; } - if (end == start || tbstart == tbend) { + TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class); + sendBeforeTextChanged(textWatchers, start, origLen, newLen); + + if (origLen == 0 || newLen == 0) { change(start, end, tb, tbstart, tbend); } else { int selstart = Selection.getSelectionStart(this); @@ -450,11 +430,6 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable checkRange("replace", start, end); moveGapTo(end); - TextWatcher[] recipients; - - int origlen = end - start; - - recipients = sendTextWillChange(start, origlen, tbend - tbstart); if (mGapLength < 2) resizeFor(length() + 1); @@ -475,9 +450,9 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable new Exception("mGapLength < 1").printStackTrace(); } - int inserted = change(false, start + 1, start + 1, tb, tbstart, tbend); - change(false, start, start + 1, "", 0, 0); - change(false, start + inserted, start + inserted + origlen, "", 0, 0); + change(start + 1, start + 1, tb, tbstart, tbend); + change(start, start + 1, "", 0, 0); + change(start + newLen, start + newLen + origLen, "", 0, 0); /* * Special case to keep the cursor in the same position @@ -490,7 +465,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (selstart > start && selstart < end) { long off = selstart - start; - off = off * inserted / (end - start); + off = off * newLen / (end - start); selstart = (int) off + start; setSpan(false, Selection.SELECTION_START, selstart, selstart, @@ -499,15 +474,16 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (selend > start && selend < end) { long off = selend - start; - off = off * inserted / (end - start); + off = off * newLen / (end - start); selend = (int) off + start; setSpan(false, Selection.SELECTION_END, selend, selend, Spanned.SPAN_POINT_POINT); } - sendTextChange(recipients, start, origlen, inserted); - sendTextHasChanged(recipients); } + sendTextChanged(textWatchers, start, origLen, newLen); + sendAfterTextChanged(textWatchers); + return this; } @@ -579,8 +555,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable mSpanEnds[i] = end; mSpanFlags[i] = flags; - if (send) - sendSpanChanged(what, ostart, oend, nstart, nend); + if (send) sendSpanChanged(what, ostart, oend, nstart, nend); return; } @@ -610,8 +585,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable mSpanFlags[mSpanCount] = flags; mSpanCount++; - if (send) - sendSpanAdded(what, nstart, nend); + if (send) sendSpanAdded(what, nstart, nend); } /** @@ -874,30 +848,27 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable return new String(buf); } - private TextWatcher[] sendTextWillChange(int start, int before, int after) { - TextWatcher[] recip = getSpans(start, start + before, TextWatcher.class); - int n = recip.length; + private void sendBeforeTextChanged(TextWatcher[] watchers, int start, int before, int after) { + int n = watchers.length; for (int i = 0; i < n; i++) { - recip[i].beforeTextChanged(this, start, before, after); + watchers[i].beforeTextChanged(this, start, before, after); } - - return recip; } - private void sendTextChange(TextWatcher[] recip, int start, int before, int after) { - int n = recip.length; + private void sendTextChanged(TextWatcher[] watchers, int start, int before, int after) { + int n = watchers.length; for (int i = 0; i < n; i++) { - recip[i].onTextChanged(this, start, before, after); + watchers[i].onTextChanged(this, start, before, after); } } - private void sendTextHasChanged(TextWatcher[] recip) { - int n = recip.length; + private void sendAfterTextChanged(TextWatcher[] watchers) { + int n = watchers.length; for (int i = 0; i < n; i++) { - recip[i].afterTextChanged(this); + watchers[i].afterTextChanged(this); } } @@ -1039,8 +1010,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable * Don't call this yourself -- exists for Canvas to use internally. * {@hide} */ - public void drawText(Canvas c, int start, int end, - float x, float y, Paint p) { + public void drawText(Canvas c, int start, int end, float x, float y, Paint p) { checkRange("drawText", start, end); if (end <= mGapStart) { @@ -1061,8 +1031,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable * Don't call this yourself -- exists for Canvas to use internally. * {@hide} */ - public void drawTextRun(Canvas c, int start, int end, - int contextStart, int contextEnd, + public void drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd, float x, float y, int flags, Paint p) { checkRange("drawTextRun", start, end); @@ -1264,6 +1233,7 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable private int[] mSpanFlags; private int mSpanCount; + // TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned} private static final int POINT = 2; private static final int PARAGRAPH = 3; diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java index e2aafa93eaa1..33631b774d9b 100644 --- a/core/java/android/view/DisplayList.java +++ b/core/java/android/view/DisplayList.java @@ -16,6 +16,8 @@ package android.view; +import android.graphics.Matrix; + /** * A display lists records a series of graphics related operation and can replay * them later. Display lists are usually built by recording operations on a @@ -117,12 +119,26 @@ public abstract class DisplayList { public abstract void setClipChildren(boolean clipChildren); /** - * Set the application scale on the DisplayList. This scale is incurred by applications that - * are auto-scaled for compatibility reasons. By default, the value is 1 (unscaled). + * Set the static matrix on the DisplayList. This matrix exists if a custom ViewGroup + * overrides + * {@link ViewGroup#getChildStaticTransformation(View, android.view.animation.Transformation)} + * and also has {@link ViewGroup#setStaticTransformationsEnabled(boolean)} set to true. + * This matrix will be concatenated with any other matrices in the DisplayList to position + * the view appropriately. + * + * @param matrix The matrix + */ + public abstract void setStaticMatrix(Matrix matrix); + + /** + * Set the Animation matrix on the DisplayList. This matrix exists if an Animation is + * currently playing on a View, and is set on the DisplayList during at draw() time. When + * the Animation finishes, the matrix should be cleared by sending <code>null</code> + * for the matrix parameter. * - * @param scale The scaling factor + * @param matrix The matrix, null indicates that the matrix should be cleared. */ - public abstract void setApplicationScale(float scale); + public abstract void setAnimationMatrix(Matrix matrix); /** * Sets the alpha value for the DisplayList diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index 5b0433eaf14c..bedafc74da67 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -259,6 +259,13 @@ class GLES20Canvas extends HardwareCanvas { private static native int nCallDrawGLFunction(int renderer, int drawGLFunction); + @Override + public int invokeFunctors(Rect dirty) { + return nInvokeFunctors(mRenderer, dirty); + } + + private static native int nInvokeFunctors(int renderer, Rect dirty); + /////////////////////////////////////////////////////////////////////////// // Memory /////////////////////////////////////////////////////////////////////////// diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java index 9b4cf216fdea..bc3bce0cfdaf 100644 --- a/core/java/android/view/GLES20DisplayList.java +++ b/core/java/android/view/GLES20DisplayList.java @@ -17,6 +17,7 @@ package android.view; import android.graphics.Bitmap; +import android.graphics.Matrix; import java.util.ArrayList; @@ -119,9 +120,18 @@ class GLES20DisplayList extends DisplayList { } @Override - public void setApplicationScale(float scale) { + public void setStaticMatrix(Matrix matrix) { try { - nSetApplicationScale(getNativeDisplayList(), scale); + nSetStaticMatrix(getNativeDisplayList(), matrix.native_instance); + } catch (IllegalStateException e) { + // invalid DisplayList okay: we'll set current values the next time we render to it + } + } + + @Override + public void setAnimationMatrix(Matrix matrix) { + try { + nSetAnimationMatrix(getNativeDisplayList(), matrix.native_instance); } catch (IllegalStateException e) { // invalid DisplayList okay: we'll set current values the next time we render to it } @@ -335,6 +345,8 @@ class GLES20DisplayList extends DisplayList { private static native void nSetTransformationInfo(int displayList, float alpha, float translationX, float translationY, float rotation, float rotationX, float rotationY, float scaleX, float scaleY); + private static native void nSetStaticMatrix(int displayList, int nativeMatrix); + private static native void nSetAnimationMatrix(int displayList, int animationMatrix); /////////////////////////////////////////////////////////////////////////// diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java index 2636ea2172a5..de8c62de4f35 100644 --- a/core/java/android/view/HardwareCanvas.java +++ b/core/java/android/view/HardwareCanvas.java @@ -98,4 +98,16 @@ public abstract class HardwareCanvas extends Canvas { // Noop - this is done in the display list recorder subclass return DisplayList.STATUS_DONE; } + + /** + * Invoke all the functors who requested to be invoked during the previous frame. + * + * @param dirty The region to redraw when the functors return {@link DisplayList#STATUS_DRAW} + * + * @return One of {@link DisplayList#STATUS_DONE}, {@link DisplayList#STATUS_DRAW} or + * {@link DisplayList#STATUS_INVOKE} + */ + public int invokeFunctors(Rect dirty) { + return DisplayList.STATUS_DONE; + } } diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 81c7ebff8bea..b100a0c50113 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -259,7 +259,14 @@ public abstract class HardwareRenderer { * @param pw */ abstract void dumpGfxInfo(PrintWriter pw); - + + /** + * Outputs the total number of frames rendered (used for fps calculations) + * + * @return the number of frames rendered + */ + abstract long getFrameCount(); + /** * Sets the directory to use as a persistent storage for hardware rendering * resources. @@ -274,12 +281,13 @@ public abstract class HardwareRenderer { /** * Notifies EGL that the frame is about to be rendered. + * @param size */ - private static void beginFrame() { - nBeginFrame(); + private static void beginFrame(int[] size) { + nBeginFrame(size); } - private static native void nBeginFrame(); + private static native void nBeginFrame(int[] size); /** * Preserves the back buffer of the current surface after a buffer swap. @@ -494,7 +502,9 @@ public abstract class HardwareRenderer { static final int SURFACE_STATE_ERROR = 0; static final int SURFACE_STATE_SUCCESS = 1; static final int SURFACE_STATE_UPDATED = 2; - + + static final int FUNCTOR_PROCESS_DELAY = 2; + static EGL10 sEgl; static EGLDisplay sEglDisplay; static EGLConfig sEglConfig; @@ -512,7 +522,7 @@ public abstract class HardwareRenderer { GL mGl; HardwareCanvas mCanvas; - int mFrameCount; + long mFrameCount; Paint mDebugPaint; static boolean sDirtyRegions; @@ -542,6 +552,9 @@ public abstract class HardwareRenderer { private final Rect mRedrawClip = new Rect(); + private final int[] mSurfaceSize = new int[2]; + private final FunctorsRunnable mFunctorsRunnable = new FunctorsRunnable(); + GlRenderer(int glVersion, boolean translucent) { mGlVersion = glVersion; mTranslucent = translucent; @@ -589,6 +602,11 @@ public abstract class HardwareRenderer { } } + @Override + long getFrameCount() { + return mFrameCount; + } + /** * Indicates whether this renderer instance can track and update dirty regions. */ @@ -943,6 +961,24 @@ public abstract class HardwareRenderer { void onPostDraw() { } + class FunctorsRunnable implements Runnable { + View.AttachInfo attachInfo; + + @Override + public void run() { + final HardwareRenderer renderer = attachInfo.mHardwareRenderer; + if (renderer == null || !renderer.isEnabled() || renderer != GlRenderer.this) { + return; + } + + final int surfaceState = checkCurrent(); + if (surfaceState != SURFACE_STATE_ERROR) { + int status = mCanvas.invokeFunctors(mRedrawClip); + handleFunctorStatus(attachInfo, status); + } + } + } + @Override boolean draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty) { @@ -957,17 +993,29 @@ public abstract class HardwareRenderer { final int surfaceState = checkCurrent(); if (surfaceState != SURFACE_STATE_ERROR) { + HardwareCanvas canvas = mCanvas; + attachInfo.mHardwareCanvas = canvas; + // We had to change the current surface and/or context, redraw everything if (surfaceState == SURFACE_STATE_UPDATED) { dirty = null; + beginFrame(null); + } else { + int[] size = mSurfaceSize; + beginFrame(size); + + if (size[1] != mHeight || size[0] != mWidth) { + mWidth = size[0]; + mHeight = size[1]; + + canvas.setViewport(mWidth, mHeight); + + dirty = null; + } } - beginFrame(); onPreDraw(dirty); - HardwareCanvas canvas = mCanvas; - attachInfo.mHardwareCanvas = canvas; - int saveCount = canvas.save(); callbacks.onHardwarePreDraw(canvas); @@ -1025,15 +1073,7 @@ public abstract class HardwareRenderer { } } - if (status != DisplayList.STATUS_DONE) { - if (mRedrawClip.isEmpty()) { - attachInfo.mViewRootImpl.invalidate(); - } else { - attachInfo.mViewRootImpl.invalidateChildInParent( - null, mRedrawClip); - mRedrawClip.setEmpty(); - } - } + handleFunctorStatus(attachInfo, status); } else { // Shouldn't reach here view.draw(canvas); @@ -1042,13 +1082,13 @@ public abstract class HardwareRenderer { callbacks.onHardwarePostDraw(canvas); canvas.restoreToCount(saveCount); view.mRecreateDisplayList = false; - + mFrameCount++; if (mDebugDirtyRegions) { if (mDebugPaint == null) { mDebugPaint = new Paint(); mDebugPaint.setColor(0x7fff0000); } - if (dirty != null && (mFrameCount++ & 1) == 0) { + if (dirty != null && (mFrameCount & 1) == 0) { canvas.drawRect(dirty, mDebugPaint); } } @@ -1085,6 +1125,26 @@ public abstract class HardwareRenderer { return false; } + private void handleFunctorStatus(View.AttachInfo attachInfo, int status) { + // If the draw flag is set, functors will be invoked while executing + // the tree of display lists + if ((status & DisplayList.STATUS_DRAW) != 0) { + if (mRedrawClip.isEmpty()) { + attachInfo.mViewRootImpl.invalidate(); + } else { + attachInfo.mViewRootImpl.invalidateChildInParent(null, mRedrawClip); + mRedrawClip.setEmpty(); + } + } + + if ((status & DisplayList.STATUS_INVOKE) != 0) { + attachInfo.mHandler.removeCallbacks(mFunctorsRunnable); + mFunctorsRunnable.attachInfo = attachInfo; + // delay the functor callback by a few ms so it isn't polled constantly + attachInfo.mHandler.postDelayed(mFunctorsRunnable, FUNCTOR_PROCESS_DELAY); + } + } + /** * Ensures the current EGL context is the one we expect. * diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index 104ed6ac6829..e4a4a7569dc8 100755 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -591,8 +591,24 @@ public class KeyEvent extends InputEvent implements Parcelable { /** Key code constant: Calculator special function key. * Used to launch a calculator application. */ public static final int KEYCODE_CALCULATOR = 210; - - private static final int LAST_KEYCODE = KEYCODE_CALCULATOR; + /** Key code constant: Japanese full-width / half-width key. */ + public static final int KEYCODE_ZENKAKU_HANKAKU = 211; + /** Key code constant: Japanese alphanumeric key. */ + public static final int KEYCODE_EISU = 212; + /** Key code constant: Japanese non-conversion key. */ + public static final int KEYCODE_MUHENKAN = 213; + /** Key code constant: Japanese conversion key. */ + public static final int KEYCODE_HENKAN = 214; + /** Key code constant: Japanese katakana / hiragana key. */ + public static final int KEYCODE_KATAKANA_HIRAGANA = 215; + /** Key code constant: Japanese Yen key. */ + public static final int KEYCODE_YEN = 216; + /** Key code constant: Japanese Ro key. */ + public static final int KEYCODE_RO = 217; + /** Key code constant: Japanese kana key. */ + public static final int KEYCODE_KANA = 218; + + private static final int LAST_KEYCODE = KEYCODE_KANA; // NOTE: If you add a new keycode here you must also add it to: // isSystem() @@ -825,6 +841,14 @@ public class KeyEvent extends InputEvent implements Parcelable { names.append(KEYCODE_CALENDAR, "KEYCODE_CALENDAR"); names.append(KEYCODE_MUSIC, "KEYCODE_MUSIC"); names.append(KEYCODE_CALCULATOR, "KEYCODE_CALCULATOR"); + names.append(KEYCODE_ZENKAKU_HANKAKU, "KEYCODE_ZENKAKU_HANKAKU"); + names.append(KEYCODE_EISU, "KEYCODE_EISU"); + names.append(KEYCODE_MUHENKAN, "KEYCODE_MUHENKAN"); + names.append(KEYCODE_HENKAN, "KEYCODE_HENKAN"); + names.append(KEYCODE_KATAKANA_HIRAGANA, "KEYCODE_KATAKANA_HIRAGANA"); + names.append(KEYCODE_YEN, "KEYCODE_YEN"); + names.append(KEYCODE_RO, "KEYCODE_RO"); + names.append(KEYCODE_KANA, "KEYCODE_KANA"); }; // Symbolic names of all metakeys in bit order from least significant to most significant. diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 6c964b017c09..18e1697b2665 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -765,7 +765,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ static final int FILTER_TOUCHES_WHEN_OBSCURED = 0x00000400; - // note flag value 0x00000800 is now available for next flags... + /** + * Set for framework elements that use FITS_SYSTEM_WINDOWS, to indicate + * that they are optional and should be skipped if the window has + * requested system UI flags that ignore those insets for layout. + */ + static final int OPTIONAL_FITS_SYSTEM_WINDOWS = 0x00000800; /** * <p>This view doesn't show fading edges.</p> @@ -1459,7 +1464,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * apps. * @hide */ - public static final boolean USE_DISPLAY_LIST_PROPERTIES = false; + public static final boolean USE_DISPLAY_LIST_PROPERTIES = true; /** * Map used to store views' tags. @@ -1909,28 +1914,31 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public static final int OVER_SCROLL_NEVER = 2; /** - * View has requested the system UI (status bar) to be visible (the default). + * Special constant for {@link #setSystemUiVisibility(int)}: View has + * requested the system UI (status bar) to be visible (the default). * * @see #setSystemUiVisibility(int) */ public static final int SYSTEM_UI_FLAG_VISIBLE = 0; /** - * View has requested the system UI to enter an unobtrusive "low profile" mode. + * Flag for {@link #setSystemUiVisibility(int)}: View has requested the + * system UI to enter an unobtrusive "low profile" mode. * - * This is for use in games, book readers, video players, or any other "immersive" application - * where the usual system chrome is deemed too distracting. + * <p>This is for use in games, book readers, video players, or any other + * "immersive" application where the usual system chrome is deemed too distracting. * - * In low profile mode, the status bar and/or navigation icons may dim. + * <p>In low profile mode, the status bar and/or navigation icons may dim. * * @see #setSystemUiVisibility(int) */ public static final int SYSTEM_UI_FLAG_LOW_PROFILE = 0x00000001; /** - * View has requested that the system navigation be temporarily hidden. + * Flag for {@link #setSystemUiVisibility(int)}: View has requested that the + * system navigation be temporarily hidden. * - * This is an even less obtrusive state than that called for by + * <p>This is an even less obtrusive state than that called for by * {@link #SYSTEM_UI_FLAG_LOW_PROFILE}; on devices that draw essential navigation controls * (Home, Back, and the like) on screen, <code>SYSTEM_UI_FLAG_HIDE_NAVIGATION</code> will cause * those to disappear. This is useful (in conjunction with the @@ -1938,14 +1946,92 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN FLAG_LAYOUT_IN_SCREEN} * window flags) for displaying content using every last pixel on the display. * - * There is a limitation: because navigation controls are so important, the least user - * interaction will cause them to reappear immediately. + * <p>There is a limitation: because navigation controls are so important, the least user + * interaction will cause them to reappear immediately. When this happens, both + * this flag and {@link #SYSTEM_UI_FLAG_FULLSCREEN} will be cleared automatically, + * so that both elements reappear at the same time. * * @see #setSystemUiVisibility(int) */ public static final int SYSTEM_UI_FLAG_HIDE_NAVIGATION = 0x00000002; /** + * Flag for {@link #setSystemUiVisibility(int)}: View has requested to go + * into the normal fullscreen mode so that its content can take over the screen + * while still allowing the user to interact with the application. + * + * <p>This has the same visual effect as + * {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN + * WindowManager.LayoutParams.FLAG_FULLSCREEN}, + * meaning that non-critical screen decorations (such as the status bar) will be + * hidden while the user is in the View's window, focusing the experience on + * that content. Unlike the window flag, if you are using ActionBar in + * overlay mode with {@link Window#FEATURE_ACTION_BAR_OVERLAY + * Window.FEATURE_ACTION_BAR_OVERLAY}, then enabling this flag will also + * hide the action bar. + * + * <p>This approach to going fullscreen is best used over the window flag when + * it is a transient state -- that is, the application does this at certain + * points in its user interaction where it wants to allow the user to focus + * on content, but not as a continuous state. For situations where the application + * would like to simply stay full screen the entire time (such as a game that + * wants to take over the screen), the + * {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN window flag} + * is usually a better approach. The state set here will be removed by the system + * in various situations (such as the user moving to another application) like + * the other system UI states. + * + * <p>When using this flag, the application should provide some easy facility + * for the user to go out of it. A common example would be in an e-book + * reader, where tapping on the screen brings back whatever screen and UI + * decorations that had been hidden while the user was immersed in reading + * the book. + * + * @see #setSystemUiVisibility(int) + */ + public static final int SYSTEM_UI_FLAG_FULLSCREEN = 0x00000004; + + /** + * Flag for {@link #setSystemUiVisibility(int)}: When using other layout + * flags, we would like a stable view of the content insets given to + * {@link #fitSystemWindows(Rect)}. This means that the insets seen there + * will always represent the worst case that the application can expect + * as a continue state. In practice this means with any of system bar, + * nav bar, and status bar shown, but not the space that would be needed + * for an input method. + * + * <p>If you are using ActionBar in + * overlay mode with {@link Window#FEATURE_ACTION_BAR_OVERLAY + * Window.FEATURE_ACTION_BAR_OVERLAY}, this flag will also impact the + * insets it adds to those given to the application. + */ + public static final int SYSTEM_UI_FLAG_LAYOUT_STABLE = 0x00000100; + + /** + * Flag for {@link #setSystemUiVisibility(int)}: View would like its window + * to be layed out as if it has requested + * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, even if it currently hasn't. This + * allows it to avoid artifacts when switching in and out of that mode, at + * the expense that some of its user interface may be covered by screen + * decorations when they are shown. You can perform layout of your inner + * UI elements to account for the navagation system UI through the + * {@link #fitSystemWindows(Rect)} method. + */ + public static final int SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = 0x00000200; + + /** + * Flag for {@link #setSystemUiVisibility(int)}: View would like its window + * to be layed out as if it has requested + * {@link #SYSTEM_UI_FLAG_FULLSCREEN}, even if it currently hasn't. This + * allows it to avoid artifacts when switching in and out of that mode, at + * the expense that some of its user interface may be covered by screen + * decorations when they are shown. You can perform layout of your inner + * UI elements to account for non-fullscreen system UI through the + * {@link #fitSystemWindows(Rect)} method. + */ + public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400; + + /** * @deprecated Use {@link #SYSTEM_UI_FLAG_LOW_PROFILE} instead. */ public static final int STATUS_BAR_HIDDEN = SYSTEM_UI_FLAG_LOW_PROFILE; @@ -2055,17 +2141,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * @hide - * - * NOTE: This flag may only be used in subtreeSystemUiVisibility, etc. etc. - * - * This hides HOME and RECENT and is provided for compatibility with interim implementations. - */ - @Deprecated - public static final int STATUS_BAR_DISABLE_NAVIGATION = - STATUS_BAR_DISABLE_HOME | STATUS_BAR_DISABLE_RECENT; - - /** - * @hide */ public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x0000FFFF; @@ -2076,7 +2151,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @hide */ public static final int SYSTEM_UI_CLEARABLE_FLAGS = - SYSTEM_UI_FLAG_LOW_PROFILE | SYSTEM_UI_FLAG_HIDE_NAVIGATION; + SYSTEM_UI_FLAG_LOW_PROFILE | SYSTEM_UI_FLAG_HIDE_NAVIGATION + | SYSTEM_UI_FLAG_FULLSCREEN; + + /** + * Flags that can impact the layout in relation to system UI. + */ + public static final int SYSTEM_UI_LAYOUT_FLAGS = + SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; /** * Find views that render the specified text. @@ -4692,21 +4775,54 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** - * Apply the insets for system windows to this view, if the FITS_SYSTEM_WINDOWS flag - * is set + * Called by the view hierarchy when the content insets for a window have + * changed, to allow it to adjust its content to fit within those windows. + * The content insets tell you the space that the status bar, input method, + * and other system windows infringe on the application's window. + * + * <p>You do not normally need to deal with this function, since the default + * window decoration given to applications takes care of applying it to the + * content of the window. If you use {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} + * or {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION} this will not be the case, + * and your content can be placed under those system elements. You can then + * use this method within your view hierarchy if you have parts of your UI + * which you would like to ensure are not being covered. * - * @param insets Insets for system windows + * <p>The default implementation of this method simply applies the content + * inset's to the view's padding. This can be enabled through + * {@link #setFitsSystemWindows(boolean)}. Alternatively, you can override + * the method and handle the insets however you would like. Note that the + * insets provided by the framework are always relative to the far edges + * of the window, not accounting for the location of the called view within + * that window. (In fact when this method is called you do not yet know + * where the layout will place the view, as it is done before layout happens.) * - * @return True if this view applied the insets, false otherwise + * <p>Note: unlike many View methods, there is no dispatch phase to this + * call. If you are overriding it in a ViewGroup and want to allow the + * call to continue to your children, you must be sure to call the super + * implementation. + * + * @param insets Current content insets of the window. Prior to + * {@link android.os.Build.VERSION_CODES#JELLY_BEAN} you must not modify + * the insets or else you and Android will be unhappy. + * + * @return Return true if this view applied the insets and it should not + * continue propagating further down the hierarchy, false otherwise. */ protected boolean fitSystemWindows(Rect insets) { if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) { - mPaddingLeft = insets.left; - mPaddingTop = insets.top; - mPaddingRight = insets.right; - mPaddingBottom = insets.bottom; - requestLayout(); - return true; + mUserPaddingStart = -1; + mUserPaddingEnd = -1; + mUserPaddingRelative = false; + if ((mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) == 0 + || mAttachInfo == null + || (mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0) { + internalSetPadding(insets.left, insets.top, insets.right, insets.bottom); + return true; + } else { + internalSetPadding(0, 0, 0, 0); + return false; + } } return false; } @@ -4742,6 +4858,23 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Ask that a new dispatch of {@link #fitSystemWindows(Rect)} be performed. + */ + public void requestFitSystemWindows() { + if (mParent != null) { + mParent.requestFitSystemWindows(); + } + } + + /** + * For use by PhoneWindow to make its own system window fitting optional. + * @hide + */ + public void makeOptionalFitsSystemWindows() { + setFlags(OPTIONAL_FITS_SYSTEM_WINDOWS, OPTIONAL_FITS_SYSTEM_WINDOWS); + } + + /** * Returns the visibility status for this view. * * @return One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. @@ -6118,19 +6251,19 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Private function to aggregate all per-view attributes in to the view * root. */ - void dispatchCollectViewAttributes(int visibility) { - performCollectViewAttributes(visibility); + void dispatchCollectViewAttributes(AttachInfo attachInfo, int visibility) { + performCollectViewAttributes(attachInfo, visibility); } - void performCollectViewAttributes(int visibility) { - if ((visibility & VISIBILITY_MASK) == VISIBLE && mAttachInfo != null) { + void performCollectViewAttributes(AttachInfo attachInfo, int visibility) { + if ((visibility & VISIBILITY_MASK) == VISIBLE) { if ((mViewFlags & KEEP_SCREEN_ON) == KEEP_SCREEN_ON) { - mAttachInfo.mKeepScreenOn = true; + attachInfo.mKeepScreenOn = true; } - mAttachInfo.mSystemUiVisibility |= mSystemUiVisibility; + attachInfo.mSystemUiVisibility |= mSystemUiVisibility; ListenerInfo li = mListenerInfo; if (li != null && li.mOnSystemUiVisibilityChangeListener != null) { - mAttachInfo.mHasSystemUiListeners = true; + attachInfo.mHasSystemUiListeners = true; } } } @@ -10126,7 +10259,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mAttachInfo.mScrollContainers.add(this); mPrivateFlags |= SCROLL_CONTAINER_ADDED; } - performCollectViewAttributes(visibility); + performCollectViewAttributes(mAttachInfo, visibility); onAttachedToWindow(); ListenerInfo li = mListenerInfo; @@ -11396,12 +11529,34 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal displayList.setClipChildren( (((ViewGroup)mParent).mGroupFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0); } - if (mAttachInfo != null && mAttachInfo.mScalingRequired && - mAttachInfo.mApplicationScale != 1.0f) { - displayList.setApplicationScale(1f / mAttachInfo.mApplicationScale); + float alpha = 1; + if (mParent instanceof ViewGroup && (((ViewGroup) mParent).mGroupFlags & + ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { + ViewGroup parentVG = (ViewGroup) mParent; + final boolean hasTransform = + parentVG.getChildStaticTransformation(this, parentVG.mChildTransformation); + if (hasTransform) { + Transformation transform = parentVG.mChildTransformation; + final int transformType = parentVG.mChildTransformation.getTransformationType(); + if (transformType != Transformation.TYPE_IDENTITY) { + if ((transformType & Transformation.TYPE_ALPHA) != 0) { + alpha = transform.getAlpha(); + } + if ((transformType & Transformation.TYPE_MATRIX) != 0) { + displayList.setStaticMatrix(transform.getMatrix()); + } + } + } } if (mTransformationInfo != null) { - displayList.setTransformationInfo(mTransformationInfo.mAlpha, + alpha *= mTransformationInfo.mAlpha; + if (alpha < 1) { + final int multipliedAlpha = (int) (255 * alpha); + if (onSetAlpha(multipliedAlpha)) { + alpha = 1; + } + } + displayList.setTransformationInfo(alpha, mTransformationInfo.mTranslationX, mTransformationInfo.mTranslationY, mTransformationInfo.mRotation, mTransformationInfo.mRotationX, mTransformationInfo.mRotationY, mTransformationInfo.mScaleX, @@ -11415,6 +11570,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal displayList.setPivotX(getPivotX()); displayList.setPivotY(getPivotY()); } + } else if (alpha < 1) { + displayList.setAlpha(alpha); } } } @@ -11447,6 +11604,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if ((flags & ViewGroup.FLAG_CHILDREN_DRAWN_WITH_CACHE) != 0 || (flags & ViewGroup.FLAG_ALWAYS_DRAWN_WITH_CACHE) != 0) { caching = true; + // Auto-scaled apps are not hw-accelerated, no need to set scaling flag on DisplayList if (mAttachInfo != null) scalingRequired = mAttachInfo.mScalingRequired; } else { caching = (layerType != LAYER_TYPE_NONE) || hardwareAccelerated; @@ -11457,7 +11615,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal more = drawAnimation(parent, drawingTime, a, scalingRequired); concatMatrix = a.willChangeTransformationMatrix(); transformToApply = parent.mChildTransformation; - } else if ((flags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { + } else if (!useDisplayListProperties && + (flags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) { final boolean hasTransform = parent.getChildStaticTransformation(this, parent.mChildTransformation); if (hasTransform) { @@ -11525,6 +11684,17 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } useDisplayListProperties &= hasDisplayList; + if (useDisplayListProperties) { + displayList = getDisplayList(); + if (!displayList.isValid()) { + // Uncommon, but possible. If a view is removed from the hierarchy during the call + // to getDisplayList(), the display list will be marked invalid and we should not + // try to use it again. + displayList = null; + hasDisplayList = false; + useDisplayListProperties = false; + } + } final boolean hasNoCache = cache == null || hasDisplayList; final boolean offsetForScroll = cache == null && !hasDisplayList && @@ -11542,6 +11712,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } if (scalingRequired) { if (useDisplayListProperties) { + // TODO: Might not need this if we put everything inside the DL restoreTo = canvas.save(); } // mAttachInfo cannot be null, otherwise scalingRequired == false @@ -11551,7 +11722,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } float alpha = useDisplayListProperties ? 1 : getAlpha(); - if (transformToApply != null || alpha < 1.0f || !hasIdentityMatrix()) { + if (transformToApply != null || alpha < 1 || !hasIdentityMatrix()) { if (transformToApply != null || !childHasIdentityMatrix) { int transX = 0; int transY = 0; @@ -11563,16 +11734,20 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (transformToApply != null) { if (concatMatrix) { - // Undo the scroll translation, apply the transformation matrix, - // then redo the scroll translate to get the correct result. - canvas.translate(-transX, -transY); - canvas.concat(transformToApply.getMatrix()); - canvas.translate(transX, transY); + if (useDisplayListProperties) { + displayList.setAnimationMatrix(transformToApply.getMatrix()); + } else { + // Undo the scroll translation, apply the transformation matrix, + // then redo the scroll translate to get the correct result. + canvas.translate(-transX, -transY); + canvas.concat(transformToApply.getMatrix()); + canvas.translate(transX, transY); + } parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION; } float transformAlpha = transformToApply.getAlpha(); - if (transformAlpha < 1.0f) { + if (transformAlpha < 1) { alpha *= transformToApply.getAlpha(); parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION; } @@ -11585,7 +11760,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } - if (alpha < 1.0f) { + if (alpha < 1) { parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION; if (hasNoCache) { final int multipliedAlpha = (int) (255 * alpha); @@ -11595,7 +11770,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal layerType != LAYER_TYPE_NONE) { layerFlags |= Canvas.CLIP_TO_LAYER_SAVE_FLAG; } - if (layerType == LAYER_TYPE_NONE) { + if (useDisplayListProperties) { + displayList.setAlpha(alpha * getAlpha()); + } else if (layerType == LAYER_TYPE_NONE) { final int scrollX = hasDisplayList ? 0 : sx; final int scrollY = hasDisplayList ? 0 : sy; canvas.saveLayerAlpha(scrollX, scrollY, scrollX + mRight - mLeft, @@ -11625,7 +11802,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } - if (hasDisplayList) { + if (!useDisplayListProperties && hasDisplayList) { displayList = getDisplayList(); if (!displayList.isValid()) { // Uncommon, but possible. If a view is removed from the hierarchy during the call @@ -11682,7 +11859,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal cachePaint.setDither(false); parent.mCachePaint = cachePaint; } - if (alpha < 1.0f) { + if (alpha < 1) { cachePaint.setAlpha((int) (alpha * 255)); parent.mGroupFlags |= ViewGroup.FLAG_ALPHA_LOWER_THAN_ONE; } else if ((flags & ViewGroup.FLAG_ALPHA_LOWER_THAN_ONE) != 0) { @@ -13481,13 +13658,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mPrivateFlags |= FORCE_LAYOUT; mPrivateFlags |= INVALIDATED; - if (mParent != null) { - if (mLayoutParams != null) { - mLayoutParams.onResolveLayoutDirection(getResolvedLayoutDirection()); - } - if (!mParent.isLayoutRequested()) { - mParent.requestLayout(); - } + if (mLayoutParams != null) { + mLayoutParams.onResolveLayoutDirection(getResolvedLayoutDirection()); + } + + if (mParent != null && !mParent.isLayoutRequested()) { + mParent.requestLayout(); } } @@ -13982,6 +14158,35 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Returns the current system UI visibility that is currently set for + * the entire window. This is the combination of the + * {@link #setSystemUiVisibility(int)} values supplied by all of the + * views in the window. + */ + public int getWindowSystemUiVisibility() { + return mAttachInfo != null ? mAttachInfo.mSystemUiVisibility : 0; + } + + /** + * Override to find out when the window's requested system UI visibility + * has changed, that is the value returned by {@link #getWindowSystemUiVisibility()}. + * This is different from the callbacks recieved through + * {@link #setOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener)} + * in that this is only telling you about the local request of the window, + * not the actual values applied by the system. + */ + public void onWindowSystemUiVisibilityChanged(int visible) { + } + + /** + * Dispatch callbacks to {@link #onWindowSystemUiVisibilityChanged(int)} down + * the view hierarchy. + */ + public void dispatchWindowSystemUiVisiblityChanged(int visible) { + onWindowSystemUiVisibilityChanged(visible); + } + + /** * Set a listener to receive callbacks when the visibility of the system bar changes. * @param l The {@link OnSystemUiVisibilityChangeListener} to receive callbacks. */ diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 30d6ec7b15c4..d5c783f6c948 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -918,7 +918,20 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } } - + + /** + * @hide + */ + @Override + public void makeOptionalFitsSystemWindows() { + super.makeOptionalFitsSystemWindows(); + final int count = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < count; i++) { + children[i].makeOptionalFitsSystemWindows(); + } + } + /** * {@inheritDoc} */ @@ -1017,13 +1030,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } @Override - void dispatchCollectViewAttributes(int visibility) { - visibility |= mViewFlags&VISIBILITY_MASK; - super.dispatchCollectViewAttributes(visibility); - final int count = mChildrenCount; - final View[] children = mChildren; - for (int i = 0; i < count; i++) { - children[i].dispatchCollectViewAttributes(visibility); + void dispatchCollectViewAttributes(AttachInfo attachInfo, int visibility) { + if ((visibility & VISIBILITY_MASK) == VISIBLE) { + super.dispatchCollectViewAttributes(attachInfo, visibility); + final int count = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < count; i++) { + final View child = children[i]; + child.dispatchCollectViewAttributes(attachInfo, + visibility | (child.mViewFlags&VISIBILITY_MASK)); + } } } @@ -1239,6 +1255,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } @Override + public void dispatchWindowSystemUiVisiblityChanged(int visible) { + super.dispatchWindowSystemUiVisiblityChanged(visible); + + final int count = mChildrenCount; + final View[] children = mChildren; + for (int i=0; i <count; i++) { + final View child = children[i]; + child.dispatchWindowSystemUiVisiblityChanged(visible); + } + } + + @Override public void dispatchSystemUiVisibilityChanged(int visible) { super.dispatchSystemUiVisibilityChanged(visible); @@ -2244,12 +2272,12 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager super.dispatchAttachedToWindow(info, visibility); mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW; - visibility |= mViewFlags & VISIBILITY_MASK; - final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { - children[i].dispatchAttachedToWindow(info, visibility); + final View child = children[i]; + child.dispatchAttachedToWindow(info, + visibility | (child.mViewFlags&VISIBILITY_MASK)); } } diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java index 8395f1ba4b8a..75e915133ce5 100644 --- a/core/java/android/view/ViewParent.java +++ b/core/java/android/view/ViewParent.java @@ -271,4 +271,10 @@ public interface ViewParent { * @hide */ public void childHasTransientStateChanged(View child, boolean hasTransientState); + + /** + * Ask that a new dispatch of {@link View#fitSystemWindows(Rect) + * View.fitSystemWindows(Rect)} be performed. + */ + public void requestFitSystemWindows(); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 4bd5b945c427..d72f3b703ada 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -223,6 +223,7 @@ public final class ViewRootImpl implements ViewParent, long mLastTraversalFinishedTimeNanos; long mLastDrawFinishedTimeNanos; boolean mWillDrawSoon; + boolean mFitSystemWindowsRequested; boolean mLayoutRequested; boolean mFirst; boolean mReportNextDraw; @@ -230,6 +231,7 @@ public final class ViewRootImpl implements ViewParent, boolean mNewSurfaceNeeded; boolean mHasHadWindowFocus; boolean mLastWasImTarget; + int mLastSystemUiVisibility; // Pool of queued input events. private static final int MAX_QUEUED_INPUT_EVENT_POOL_SIZE = 10; @@ -263,6 +265,8 @@ public final class ViewRootImpl implements ViewParent, final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets = new ViewTreeObserver.InternalInsetsInfo(); + final Rect mFitSystemWindowsInsets = new Rect(); + final Configuration mLastConfiguration = new Configuration(); final Configuration mPendingConfiguration = new Configuration(); @@ -539,6 +543,8 @@ public final class ViewRootImpl implements ViewParent, } try { mOrigWindowType = mWindowAttributes.type; + mAttachInfo.mRecomputeGlobalAttributes = true; + collectViewAttributes(); res = sWindowSession.add(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mAttachInfo.mContentInsets, mInputChannel); @@ -786,6 +792,15 @@ public final class ViewRootImpl implements ViewParent, /** * {@inheritDoc} */ + public void requestFitSystemWindows() { + checkThread(); + mFitSystemWindowsRequested = true; + scheduleTraversals(); + } + + /** + * {@inheritDoc} + */ public void requestLayout() { checkThread(); mLayoutRequested = true; @@ -974,6 +989,100 @@ public final class ViewRootImpl implements ViewParent, } } + private boolean collectViewAttributes() { + final View.AttachInfo attachInfo = mAttachInfo; + if (attachInfo.mRecomputeGlobalAttributes) { + //Log.i(TAG, "Computing view hierarchy attributes!"); + attachInfo.mRecomputeGlobalAttributes = false; + boolean oldScreenOn = attachInfo.mKeepScreenOn; + int oldVis = attachInfo.mSystemUiVisibility; + boolean oldHasSystemUiListeners = attachInfo.mHasSystemUiListeners; + attachInfo.mKeepScreenOn = false; + attachInfo.mSystemUiVisibility = 0; + attachInfo.mHasSystemUiListeners = false; + mView.dispatchCollectViewAttributes(attachInfo, 0); + if (attachInfo.mKeepScreenOn != oldScreenOn + || attachInfo.mSystemUiVisibility != oldVis + || attachInfo.mHasSystemUiListeners != oldHasSystemUiListeners) { + WindowManager.LayoutParams params = mWindowAttributes; + if (attachInfo.mKeepScreenOn) { + params.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; + } + params.subtreeSystemUiVisibility = attachInfo.mSystemUiVisibility; + params.hasSystemUiListeners = attachInfo.mHasSystemUiListeners; + mView.dispatchWindowSystemUiVisiblityChanged(attachInfo.mSystemUiVisibility); + return true; + } + } + return false; + } + + private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp, + final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) { + int childWidthMeasureSpec; + int childHeightMeasureSpec; + boolean windowSizeMayChange = false; + + if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(TAG, + "Measuring " + host + " in display " + desiredWindowWidth + + "x" + desiredWindowHeight + "..."); + + boolean goodMeasure = false; + if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) { + // On large screens, we don't want to allow dialogs to just + // stretch to fill the entire width of the screen to display + // one line of text. First try doing the layout at a smaller + // size to see if it will fit. + final DisplayMetrics packageMetrics = res.getDisplayMetrics(); + res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true); + int baseSize = 0; + if (mTmpValue.type == TypedValue.TYPE_DIMENSION) { + baseSize = (int)mTmpValue.getDimension(packageMetrics); + } + if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": baseSize=" + baseSize); + if (baseSize != 0 && desiredWindowWidth > baseSize) { + childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); + childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); + host.measure(childWidthMeasureSpec, childHeightMeasureSpec); + if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured (" + + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")"); + if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) { + goodMeasure = true; + } else { + // Didn't fit in that size... try expanding a bit. + baseSize = (baseSize+desiredWindowWidth)/2; + if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": next baseSize=" + + baseSize); + childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); + host.measure(childWidthMeasureSpec, childHeightMeasureSpec); + if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured (" + + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")"); + if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) { + if (DEBUG_DIALOG) Log.v(TAG, "Good!"); + goodMeasure = true; + } + } + } + } + + if (!goodMeasure) { + childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); + childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); + host.measure(childWidthMeasureSpec, childHeightMeasureSpec); + if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) { + windowSizeMayChange = true; + } + } + + if (DBG) { + System.out.println("======================================"); + System.out.println("performTraversals -- after measure"); + host.debug(); + } + + return windowSizeMayChange; + } + private void performTraversals() { // cache mView since it is used so much below... final View host = mView; @@ -995,8 +1104,6 @@ public final class ViewRootImpl implements ViewParent, int desiredWindowWidth; int desiredWindowHeight; - int childWidthMeasureSpec; - int childHeightMeasureSpec; final View.AttachInfo attachInfo = mAttachInfo; @@ -1057,15 +1164,14 @@ public final class ViewRootImpl implements ViewParent, attachInfo.mHasWindowFocus = false; attachInfo.mWindowVisibility = viewVisibility; attachInfo.mRecomputeGlobalAttributes = false; - attachInfo.mKeepScreenOn = false; - attachInfo.mSystemUiVisibility = 0; viewVisibilityChanged = false; mLastConfiguration.setTo(host.getResources().getConfiguration()); + mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility; host.dispatchAttachedToWindow(attachInfo, 0); + mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets); + host.fitSystemWindows(mFitSystemWindowsInsets); //Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn); - host.fitSystemWindows(mAttachInfo.mContentInsets); - } else { desiredWindowWidth = frame.width(); desiredWindowHeight = frame.height(); @@ -1093,7 +1199,8 @@ public final class ViewRootImpl implements ViewParent, boolean insetsChanged = false; - if (mLayoutRequested && !mStopped) { + boolean layoutRequested = mLayoutRequested && !mStopped; + if (layoutRequested) { // Execute enqueued actions on every layout in case a view that was detached // enqueued an action after being detached getRunQueue().executeActions(attachInfo.mHandler); @@ -1134,79 +1241,12 @@ public final class ViewRootImpl implements ViewParent, } // Ask host how big it wants to be - if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(TAG, - "Measuring " + host + " in display " + desiredWindowWidth - + "x" + desiredWindowHeight + "..."); - - boolean goodMeasure = false; - if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) { - // On large screens, we don't want to allow dialogs to just - // stretch to fill the entire width of the screen to display - // one line of text. First try doing the layout at a smaller - // size to see if it will fit. - final DisplayMetrics packageMetrics = res.getDisplayMetrics(); - res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true); - int baseSize = 0; - if (mTmpValue.type == TypedValue.TYPE_DIMENSION) { - baseSize = (int)mTmpValue.getDimension(packageMetrics); - } - if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": baseSize=" + baseSize); - if (baseSize != 0 && desiredWindowWidth > baseSize) { - childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); - childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); - host.measure(childWidthMeasureSpec, childHeightMeasureSpec); - if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured (" - + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")"); - if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) { - goodMeasure = true; - } else { - // Didn't fit in that size... try expanding a bit. - baseSize = (baseSize+desiredWindowWidth)/2; - if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": next baseSize=" - + baseSize); - childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); - host.measure(childWidthMeasureSpec, childHeightMeasureSpec); - if (DEBUG_DIALOG) Log.v(TAG, "Window " + mView + ": measured (" - + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")"); - if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) { - if (DEBUG_DIALOG) Log.v(TAG, "Good!"); - goodMeasure = true; - } - } - } - } - - if (!goodMeasure) { - childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); - childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); - host.measure(childWidthMeasureSpec, childHeightMeasureSpec); - if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) { - windowSizeMayChange = true; - } - } - - if (DBG) { - System.out.println("======================================"); - System.out.println("performTraversals -- after measure"); - host.debug(); - } + windowSizeMayChange |= measureHierarchy(host, lp, res, + desiredWindowWidth, desiredWindowHeight); } - if (attachInfo.mRecomputeGlobalAttributes && host.mAttachInfo != null) { - //Log.i(TAG, "Computing view hierarchy attributes!"); - attachInfo.mRecomputeGlobalAttributes = false; - boolean oldScreenOn = attachInfo.mKeepScreenOn; - int oldVis = attachInfo.mSystemUiVisibility; - boolean oldHasSystemUiListeners = attachInfo.mHasSystemUiListeners; - attachInfo.mKeepScreenOn = false; - attachInfo.mSystemUiVisibility = 0; - attachInfo.mHasSystemUiListeners = false; - host.dispatchCollectViewAttributes(0); - if (attachInfo.mKeepScreenOn != oldScreenOn - || attachInfo.mSystemUiVisibility != oldVis - || attachInfo.mHasSystemUiListeners != oldHasSystemUiListeners) { - params = lp; - } + if (collectViewAttributes()) { + params = lp; } if (attachInfo.mForceReportNewAttributes) { attachInfo.mForceReportNewAttributes = false; @@ -1245,7 +1285,28 @@ public final class ViewRootImpl implements ViewParent, } } - boolean windowShouldResize = mLayoutRequested && windowSizeMayChange + if (mFitSystemWindowsRequested) { + mFitSystemWindowsRequested = false; + mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets); + host.fitSystemWindows(mFitSystemWindowsInsets); + if (mLayoutRequested) { + // Short-circuit catching a new layout request here, so + // we don't need to go through two layout passes when things + // change due to fitting system windows, which can happen a lot. + windowSizeMayChange |= measureHierarchy(host, lp, + mView.getContext().getResources(), + desiredWindowWidth, desiredWindowHeight); + } + } + + if (layoutRequested) { + // Clear this now, so that if anything requests a layout in the + // rest of this function we will catch it and re-run a full + // layout pass. + mLayoutRequested = false; + } + + boolean windowShouldResize = layoutRequested && windowSizeMayChange && ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT && frame.width() < desiredWindowWidth && frame.width() != mWidth) @@ -1285,15 +1346,6 @@ public final class ViewRootImpl implements ViewParent, boolean hadSurface = mSurface.isValid(); try { - int fl = 0; - if (params != null) { - fl = params.flags; - if (attachInfo.mKeepScreenOn) { - params.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; - } - params.subtreeSystemUiVisibility = attachInfo.mSystemUiVisibility; - params.hasSystemUiListeners = attachInfo.mHasSystemUiListeners; - } if (DEBUG_LAYOUT) { Log.i(TAG, "host=w:" + host.getMeasuredWidth() + ", h:" + host.getMeasuredHeight() + ", params=" + params); @@ -1302,10 +1354,6 @@ public final class ViewRootImpl implements ViewParent, final int surfaceGenerationId = mSurface.getGenerationId(); relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); - if (params != null) { - params.flags = fl; - } - if (DEBUG_LAYOUT) Log.v(TAG, "relayout: frame=" + frame.toShortString() + " content=" + mPendingContentInsets.toShortString() + " visible=" + mPendingVisibleInsets.toShortString() @@ -1323,7 +1371,9 @@ public final class ViewRootImpl implements ViewParent, visibleInsetsChanged = !mPendingVisibleInsets.equals( mAttachInfo.mVisibleInsets); if (contentInsetsChanged) { - if (mWidth > 0 && mHeight > 0 && + if (mWidth > 0 && mHeight > 0 && lp != null && + ((lp.systemUiVisibility|lp.subtreeSystemUiVisibility) + & View.SYSTEM_UI_LAYOUT_FLAGS) == 0 && mSurface != null && mSurface.isValid() && !mAttachInfo.mTurnOffWindowResizeAnim && mAttachInfo.mHardwareRenderer != null && @@ -1390,10 +1440,16 @@ public final class ViewRootImpl implements ViewParent, } } mAttachInfo.mContentInsets.set(mPendingContentInsets); - host.fitSystemWindows(mAttachInfo.mContentInsets); if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: " + mAttachInfo.mContentInsets); } + if (contentInsetsChanged || mLastSystemUiVisibility != + mAttachInfo.mSystemUiVisibility || mFitSystemWindowsRequested) { + mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility; + mFitSystemWindowsRequested = false; + mFitSystemWindowsInsets.set(mAttachInfo.mContentInsets); + host.fitSystemWindows(mFitSystemWindowsInsets); + } if (visibleInsetsChanged) { mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: " @@ -1547,8 +1603,8 @@ public final class ViewRootImpl implements ViewParent, (relayoutResult&WindowManagerImpl.RELAYOUT_RES_IN_TOUCH_MODE) != 0); if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged) { - childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); - childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); + int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); + int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed! mWidth=" + mWidth + " measuredWidth=" + host.getMeasuredWidth() @@ -1586,12 +1642,12 @@ public final class ViewRootImpl implements ViewParent, host.measure(childWidthMeasureSpec, childHeightMeasureSpec); } - mLayoutRequested = true; + layoutRequested = true; } } } - final boolean didLayout = mLayoutRequested && !mStopped; + final boolean didLayout = layoutRequested && !mStopped; boolean triggerGlobalLayoutListener = didLayout || attachInfo.mRecomputeGlobalAttributes; if (didLayout) { @@ -3579,9 +3635,6 @@ public final class ViewRootImpl implements ViewParent, if (mView == null) return; if (args.localChanges != 0) { if (mAttachInfo != null) { - mAttachInfo.mSystemUiVisibility = - (mAttachInfo.mSystemUiVisibility & ~args.localChanges) | - (args.localValue & args.localChanges); mAttachInfo.mRecomputeGlobalAttributes = true; } mView.updateLocalSystemUiVisibility(args.localValue, args.localChanges); @@ -4056,9 +4109,13 @@ public final class ViewRootImpl implements ViewParent, mChoreographer.removeCallbacks(Choreographer.CALLBACK_INPUT, mConsumedBatchedInputRunnable, null); } - if (mInputEventReceiver != null) { - mInputEventReceiver.consumeBatchedInputEvents(); - } + } + + // Always consume batched input events even if not scheduled, because there + // might be new input there waiting for us that we have no noticed yet because + // the Looper has not had a chance to run again. + if (mInputEventReceiver != null) { + mInputEventReceiver.consumeBatchedInputEvents(); } } diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index a99ac03e0570..b0e90db00110 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -75,6 +75,16 @@ public abstract class Window { * over how the Action Bar is displayed, such as letting application content scroll beneath * an Action Bar with a transparent background or otherwise displaying a transparent/translucent * Action Bar over application content. + * + * <p>This mode is especially useful with {@link View#SYSTEM_UI_FLAG_FULLSCREEN + * View.SYSTEM_UI_FLAG_FULLSCREEN}, which allows you to seamlessly hide the + * action bar in conjunction with other screen decorations. + * + * <p>As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}, when an + * ActionBar is in this mode it will adjust the insets provided to + * {@link View#fitSystemWindows(android.graphics.Rect) View.fitSystemWindows(Rect)} + * to include the content covered by the action bar, so you can do layout within + * that space. */ public static final int FEATURE_ACTION_BAR_OVERLAY = 9; /** diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index f2ee9f9d6a4c..a45a87ec53c6 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -510,8 +510,13 @@ public class WindowManagerImpl implements WindowManager { String name = root.getClass().getName() + '@' + Integer.toHexString(hashCode()); - pw.printf(" %s: %d views, %.2f kB (display lists)\n", + pw.printf(" %s: %d views, %.2f kB (display lists)", name, info[0], info[1] / 1024.0f); + HardwareRenderer renderer = root.getView().mAttachInfo.mHardwareRenderer; + if (renderer != null) { + pw.printf(", %d frames rendered", renderer.getFrameCount()); + } + pw.printf("\n"); viewsCount += info[0]; displayListsSize += info[1]; diff --git a/core/java/android/webkit/WebSettingsClassic.java b/core/java/android/webkit/WebSettingsClassic.java index c41bc00045b0..94b46fc9bb04 100644 --- a/core/java/android/webkit/WebSettingsClassic.java +++ b/core/java/android/webkit/WebSettingsClassic.java @@ -90,6 +90,7 @@ public class WebSettingsClassic extends WebSettings { private boolean mWorkersEnabled = false; // only affects V8. private boolean mGeolocationEnabled = true; private boolean mXSSAuditorEnabled = false; + private boolean mLinkPrefetchEnabled = false; // HTML5 configuration parameters private long mAppCacheMaxSize = Long.MAX_VALUE; private String mAppCachePath = null; @@ -1305,6 +1306,16 @@ public class WebSettingsClassic extends WebSettings { } /** + * Enables/disables HTML5 link "prefetch" parameter. + */ + public synchronized void setLinkPrefetchEnabled(boolean flag) { + if (mLinkPrefetchEnabled != flag) { + mLinkPrefetchEnabled = flag; + postSync(); + } + } + + /** * @see android.webkit.WebSettings#getJavaScriptEnabled() */ @Override diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index d225594f76eb..422b48dbea49 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -1883,12 +1883,14 @@ public class WebView extends AbsoluteLayout @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); + info.setClassName(WebView.class.getName()); mProvider.getViewDelegate().onInitializeAccessibilityNodeInfo(info); } @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); + event.setClassName(WebView.class.getName()); mProvider.getViewDelegate().onInitializeAccessibilityEvent(event); } diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java index 5a0818bc75b1..45c5fa0b723f 100644 --- a/core/java/android/webkit/WebViewClassic.java +++ b/core/java/android/webkit/WebViewClassic.java @@ -1204,10 +1204,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc static final int SHOW_FULLSCREEN = 120; static final int HIDE_FULLSCREEN = 121; static final int REPLACE_BASE_CONTENT = 123; - static final int FORM_DID_BLUR = 124; static final int UPDATE_MATCH_COUNT = 126; static final int CENTER_FIT_RECT = 127; - static final int REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID = 128; static final int SET_SCROLLBAR_MODES = 129; static final int SELECTION_STRING_CHANGED = 130; static final int HIT_TEST_RESULT = 131; @@ -1274,7 +1272,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc "HIDE_FULLSCREEN", // = 121; "DOM_FOCUS_CHANGED", // = 122; "REPLACE_BASE_CONTENT", // = 123; - "FORM_DID_BLUR", // = 124; "RETURN_LABEL", // = 125; "UPDATE_MATCH_COUNT", // = 126; "CENTER_FIT_RECT", // = 127; @@ -1420,6 +1417,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // Used to notify listeners about find-on-page results. private WebView.FindListener mFindListener; + // Used to prevent resending save password message + private Message mResumeMsg; + /** * Refer to {@link WebView#requestFocusNodeHref(Message)} for more information */ @@ -1868,11 +1868,17 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc /* package */ boolean onSavePassword(String schemePlusHost, String username, String password, final Message resumeMsg) { - boolean rVal = false; - if (resumeMsg == null) { - // null resumeMsg implies saving password silently - mDatabase.setUsernamePassword(schemePlusHost, username, password); - } else { + boolean rVal = false; + if (resumeMsg == null) { + // null resumeMsg implies saving password silently + mDatabase.setUsernamePassword(schemePlusHost, username, password); + } else { + if (mResumeMsg != null) { + Log.w(LOGTAG, "onSavePassword should not be called while dialog is up"); + resumeMsg.sendToTarget(); + return true; + } + mResumeMsg = resumeMsg; final Message remember = mPrivateHandler.obtainMessage( REMEMBER_PASSWORD); remember.getData().putString("host", schemePlusHost); @@ -1894,34 +1900,46 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - resumeMsg.sendToTarget(); + if (mResumeMsg != null) { + resumeMsg.sendToTarget(); + mResumeMsg = null; + } } }) .setNeutralButton(com.android.internal.R.string.save_password_remember, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - remember.sendToTarget(); + if (mResumeMsg != null) { + remember.sendToTarget(); + mResumeMsg = null; + } } }) .setNegativeButton(com.android.internal.R.string.save_password_never, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - neverRemember.sendToTarget(); + if (mResumeMsg != null) { + neverRemember.sendToTarget(); + mResumeMsg = null; + } } }) .setOnCancelListener(new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { - resumeMsg.sendToTarget(); + if (mResumeMsg != null) { + resumeMsg.sendToTarget(); + mResumeMsg = null; + } } }).show(); // Return true so that WebViewCore will pause while the dialog is // up. rVal = true; } - return rVal; + return rVal; } @Override @@ -3877,7 +3895,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private void scrollLayerTo(int x, int y) { int dx = mScrollingLayerRect.left - x; int dy = mScrollingLayerRect.top - y; - if (dx == 0 && y == 0) { + if (dx == 0 && dy == 0) { return; } if (mSelectingText) { @@ -8267,16 +8285,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } } break; - case REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID: - displaySoftKeyboard(true); - // fall through to UPDATE_TEXT_SELECTION_MSG_ID case UPDATE_TEXT_SELECTION_MSG_ID: updateTextSelectionFromMessage(msg.arg1, msg.arg2, (WebViewCore.TextSelectionData) msg.obj); break; - case FORM_DID_BLUR: - // TODO: Figure out if this is needed for something (b/6111763) - break; case TAKE_FOCUS: int direction = msg.arg1; View focusSearch = mWebView.focusSearch(direction); diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 3eba6d7d09e7..b4ebc09b23a8 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -325,17 +325,6 @@ public final class WebViewCore { } /** - * Called by JNI. Send a message to the UI thread to hide the soft keyboard - * if the node pointed to by nodePointer is still in focus. - * @param nodePointer The node which just blurred. - */ - private void formDidBlur(int nodePointer) { - if (mWebViewClassic == null) return; - Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.FORM_DID_BLUR, - nodePointer, 0).sendToTarget(); - } - - /** * Called by JNI when the focus node changed. */ private void focusNodeChanged(int nodePointer, WebKitHitTest hitTest) { @@ -2830,7 +2819,7 @@ public final class WebViewCore { Message.obtain(mWebViewClassic.mPrivateHandler, WebViewClassic.INIT_EDIT_FIELD, initData).sendToTarget(); Message.obtain(mWebViewClassic.mPrivateHandler, - WebViewClassic.REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID, + WebViewClassic.UPDATE_TEXT_SELECTION_MSG_ID, initData.mFieldPointer, 0, new TextSelectionData(start, end, selectionPtr)) .sendToTarget(); diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java new file mode 100644 index 000000000000..880dc345dcdc --- /dev/null +++ b/core/java/android/widget/Editor.java @@ -0,0 +1,3750 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.widget; + +import android.R; +import android.content.ClipData; +import android.content.ClipData.Item; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.inputmethodservice.ExtractEditText; +import android.os.Bundle; +import android.os.Handler; +import android.os.SystemClock; +import android.provider.Settings; +import android.text.DynamicLayout; +import android.text.Editable; +import android.text.InputType; +import android.text.Layout; +import android.text.ParcelableSpan; +import android.text.Selection; +import android.text.SpanWatcher; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.StaticLayout; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.method.KeyListener; +import android.text.method.MetaKeyKeyListener; +import android.text.method.MovementMethod; +import android.text.method.PasswordTransformationMethod; +import android.text.method.WordIterator; +import android.text.style.EasyEditSpan; +import android.text.style.SuggestionRangeSpan; +import android.text.style.SuggestionSpan; +import android.text.style.TextAppearanceSpan; +import android.text.style.URLSpan; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.ActionMode; +import android.view.ActionMode.Callback; +import android.view.DisplayList; +import android.view.DragEvent; +import android.view.Gravity; +import android.view.HardwareCanvas; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.View.DragShadowBuilder; +import android.view.View.OnClickListener; +import android.view.ViewGroup.LayoutParams; +import android.view.ViewParent; +import android.view.ViewTreeObserver; +import android.view.WindowManager; +import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.TextView.Drawables; +import android.widget.TextView.OnEditorActionListener; + +import com.android.internal.util.ArrayUtils; +import com.android.internal.widget.EditableInputConnection; + +import java.text.BreakIterator; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; + +/** + * Helper class used by TextView to handle editable text views. + * + * @hide + */ +public class Editor { + static final int BLINK = 500; + private static final float[] TEMP_POSITION = new float[2]; + private static int DRAG_SHADOW_MAX_TEXT_LENGTH = 20; + + // Cursor Controllers. + InsertionPointCursorController mInsertionPointCursorController; + SelectionModifierCursorController mSelectionModifierCursorController; + ActionMode mSelectionActionMode; + boolean mInsertionControllerEnabled; + boolean mSelectionControllerEnabled; + + // Used to highlight a word when it is corrected by the IME + CorrectionHighlighter mCorrectionHighlighter; + + InputContentType mInputContentType; + InputMethodState mInputMethodState; + + DisplayList[] mTextDisplayLists; + + boolean mFrozenWithFocus; + boolean mSelectionMoved; + boolean mTouchFocusSelected; + + KeyListener mKeyListener; + int mInputType = EditorInfo.TYPE_NULL; + + boolean mDiscardNextActionUp; + boolean mIgnoreActionUpEvent; + + long mShowCursor; + Blink mBlink; + + boolean mCursorVisible = true; + boolean mSelectAllOnFocus; + boolean mTextIsSelectable; + + CharSequence mError; + boolean mErrorWasChanged; + ErrorPopup mErrorPopup; + /** + * This flag is set if the TextView tries to display an error before it + * is attached to the window (so its position is still unknown). + * It causes the error to be shown later, when onAttachedToWindow() + * is called. + */ + boolean mShowErrorAfterAttach; + + boolean mInBatchEditControllers; + + SuggestionsPopupWindow mSuggestionsPopupWindow; + SuggestionRangeSpan mSuggestionRangeSpan; + Runnable mShowSuggestionRunnable; + + final Drawable[] mCursorDrawable = new Drawable[2]; + int mCursorCount; // Current number of used mCursorDrawable: 0 (resource=0), 1 or 2 (split) + + private Drawable mSelectHandleLeft; + private Drawable mSelectHandleRight; + private Drawable mSelectHandleCenter; + + // Global listener that detects changes in the global position of the TextView + private PositionListener mPositionListener; + + float mLastDownPositionX, mLastDownPositionY; + Callback mCustomSelectionActionModeCallback; + + // Set when this TextView gained focus with some text selected. Will start selection mode. + boolean mCreatedWithASelection; + + private EasyEditSpanController mEasyEditSpanController; + + WordIterator mWordIterator; + SpellChecker mSpellChecker; + + private Rect mTempRect; + + private TextView mTextView; + + Editor(TextView textView) { + mTextView = textView; + mEasyEditSpanController = new EasyEditSpanController(); + mTextView.addTextChangedListener(mEasyEditSpanController); + } + + void onAttachedToWindow() { + if (mShowErrorAfterAttach) { + showError(); + mShowErrorAfterAttach = false; + } + + final ViewTreeObserver observer = mTextView.getViewTreeObserver(); + // No need to create the controller. + // The get method will add the listener on controller creation. + if (mInsertionPointCursorController != null) { + observer.addOnTouchModeChangeListener(mInsertionPointCursorController); + } + if (mSelectionModifierCursorController != null) { + observer.addOnTouchModeChangeListener(mSelectionModifierCursorController); + } + updateSpellCheckSpans(0, mTextView.getText().length(), + true /* create the spell checker if needed */); + } + + void onDetachedFromWindow() { + if (mError != null) { + hideError(); + } + + if (mBlink != null) { + mBlink.removeCallbacks(mBlink); + } + + if (mInsertionPointCursorController != null) { + mInsertionPointCursorController.onDetached(); + } + + if (mSelectionModifierCursorController != null) { + mSelectionModifierCursorController.onDetached(); + } + + if (mShowSuggestionRunnable != null) { + mTextView.removeCallbacks(mShowSuggestionRunnable); + } + + invalidateTextDisplayList(); + + if (mSpellChecker != null) { + mSpellChecker.closeSession(); + // Forces the creation of a new SpellChecker next time this window is created. + // Will handle the cases where the settings has been changed in the meantime. + mSpellChecker = null; + } + + hideControllers(); + } + + private void showError() { + if (mTextView.getWindowToken() == null) { + mShowErrorAfterAttach = true; + return; + } + + if (mErrorPopup == null) { + LayoutInflater inflater = LayoutInflater.from(mTextView.getContext()); + final TextView err = (TextView) inflater.inflate( + com.android.internal.R.layout.textview_hint, null); + + final float scale = mTextView.getResources().getDisplayMetrics().density; + mErrorPopup = new ErrorPopup(err, (int)(200 * scale + 0.5f), (int)(50 * scale + 0.5f)); + mErrorPopup.setFocusable(false); + // The user is entering text, so the input method is needed. We + // don't want the popup to be displayed on top of it. + mErrorPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); + } + + TextView tv = (TextView) mErrorPopup.getContentView(); + chooseSize(mErrorPopup, mError, tv); + tv.setText(mError); + + mErrorPopup.showAsDropDown(mTextView, getErrorX(), getErrorY()); + mErrorPopup.fixDirection(mErrorPopup.isAboveAnchor()); + } + + public void setError(CharSequence error, Drawable icon) { + mError = TextUtils.stringOrSpannedString(error); + mErrorWasChanged = true; + final Drawables dr = mTextView.mDrawables; + if (dr != null) { + switch (mTextView.getResolvedLayoutDirection()) { + default: + case View.LAYOUT_DIRECTION_LTR: + mTextView.setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop, icon, + dr.mDrawableBottom); + break; + case View.LAYOUT_DIRECTION_RTL: + mTextView.setCompoundDrawables(icon, dr.mDrawableTop, dr.mDrawableRight, + dr.mDrawableBottom); + break; + } + } else { + mTextView.setCompoundDrawables(null, null, icon, null); + } + + if (mError == null) { + if (mErrorPopup != null) { + if (mErrorPopup.isShowing()) { + mErrorPopup.dismiss(); + } + + mErrorPopup = null; + } + } else { + if (mTextView.isFocused()) { + showError(); + } + } + } + + private void hideError() { + if (mErrorPopup != null) { + if (mErrorPopup.isShowing()) { + mErrorPopup.dismiss(); + } + } + + mShowErrorAfterAttach = false; + } + + /** + * Returns the Y offset to make the pointy top of the error point + * at the middle of the error icon. + */ + private int getErrorX() { + /* + * The "25" is the distance between the point and the right edge + * of the background + */ + final float scale = mTextView.getResources().getDisplayMetrics().density; + + final Drawables dr = mTextView.mDrawables; + return mTextView.getWidth() - mErrorPopup.getWidth() - mTextView.getPaddingRight() - + (dr != null ? dr.mDrawableSizeRight : 0) / 2 + (int) (25 * scale + 0.5f); + } + + /** + * Returns the Y offset to make the pointy top of the error point + * at the bottom of the error icon. + */ + private int getErrorY() { + /* + * Compound, not extended, because the icon is not clipped + * if the text height is smaller. + */ + final int compoundPaddingTop = mTextView.getCompoundPaddingTop(); + int vspace = mTextView.getBottom() - mTextView.getTop() - + mTextView.getCompoundPaddingBottom() - compoundPaddingTop; + + final Drawables dr = mTextView.mDrawables; + int icontop = compoundPaddingTop + + (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2; + + /* + * The "2" is the distance between the point and the top edge + * of the background. + */ + final float scale = mTextView.getResources().getDisplayMetrics().density; + return icontop + (dr != null ? dr.mDrawableHeightRight : 0) - mTextView.getHeight() - + (int) (2 * scale + 0.5f); + } + + void createInputContentTypeIfNeeded() { + if (mInputContentType == null) { + mInputContentType = new InputContentType(); + } + } + + void createInputMethodStateIfNeeded() { + if (mInputMethodState == null) { + mInputMethodState = new InputMethodState(); + } + } + + boolean isCursorVisible() { + // The default value is true, even when there is no associated Editor + return mCursorVisible && mTextView.isTextEditable(); + } + + void prepareCursorControllers() { + boolean windowSupportsHandles = false; + + ViewGroup.LayoutParams params = mTextView.getRootView().getLayoutParams(); + if (params instanceof WindowManager.LayoutParams) { + WindowManager.LayoutParams windowParams = (WindowManager.LayoutParams) params; + windowSupportsHandles = windowParams.type < WindowManager.LayoutParams.FIRST_SUB_WINDOW + || windowParams.type > WindowManager.LayoutParams.LAST_SUB_WINDOW; + } + + boolean enabled = windowSupportsHandles && mTextView.getLayout() != null; + mInsertionControllerEnabled = enabled && isCursorVisible(); + mSelectionControllerEnabled = enabled && mTextView.textCanBeSelected(); + + if (!mInsertionControllerEnabled) { + hideInsertionPointCursorController(); + if (mInsertionPointCursorController != null) { + mInsertionPointCursorController.onDetached(); + mInsertionPointCursorController = null; + } + } + + if (!mSelectionControllerEnabled) { + stopSelectionActionMode(); + if (mSelectionModifierCursorController != null) { + mSelectionModifierCursorController.onDetached(); + mSelectionModifierCursorController = null; + } + } + } + + private void hideInsertionPointCursorController() { + if (mInsertionPointCursorController != null) { + mInsertionPointCursorController.hide(); + } + } + + /** + * Hides the insertion controller and stops text selection mode, hiding the selection controller + */ + void hideControllers() { + hideCursorControllers(); + hideSpanControllers(); + } + + private void hideSpanControllers() { + if (mEasyEditSpanController != null) { + mEasyEditSpanController.hide(); + } + } + + private void hideCursorControllers() { + if (mSuggestionsPopupWindow != null && !mSuggestionsPopupWindow.isShowingUp()) { + // Should be done before hide insertion point controller since it triggers a show of it + mSuggestionsPopupWindow.hide(); + } + hideInsertionPointCursorController(); + stopSelectionActionMode(); + } + + /** + * Create new SpellCheckSpans on the modified region. + */ + private void updateSpellCheckSpans(int start, int end, boolean createSpellChecker) { + if (mTextView.isTextEditable() && mTextView.isSuggestionsEnabled() && + !(mTextView instanceof ExtractEditText)) { + if (mSpellChecker == null && createSpellChecker) { + mSpellChecker = new SpellChecker(mTextView); + } + if (mSpellChecker != null) { + mSpellChecker.spellCheck(start, end); + } + } + } + + void onScreenStateChanged(int screenState) { + switch (screenState) { + case View.SCREEN_STATE_ON: + resumeBlink(); + break; + case View.SCREEN_STATE_OFF: + suspendBlink(); + break; + } + } + + private void suspendBlink() { + if (mBlink != null) { + mBlink.cancel(); + } + } + + private void resumeBlink() { + if (mBlink != null) { + mBlink.uncancel(); + makeBlink(); + } + } + + void adjustInputType(boolean password, boolean passwordInputType, + boolean webPasswordInputType, boolean numberPasswordInputType) { + // mInputType has been set from inputType, possibly modified by mInputMethod. + // Specialize mInputType to [web]password if we have a text class and the original input + // type was a password. + if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { + if (password || passwordInputType) { + mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION)) + | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; + } + if (webPasswordInputType) { + mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION)) + | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD; + } + } else if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_NUMBER) { + if (numberPasswordInputType) { + mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION)) + | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD; + } + } + } + + private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) { + int wid = tv.getPaddingLeft() + tv.getPaddingRight(); + int ht = tv.getPaddingTop() + tv.getPaddingBottom(); + + int defaultWidthInPixels = mTextView.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.textview_error_popup_default_width); + Layout l = new StaticLayout(text, tv.getPaint(), defaultWidthInPixels, + Layout.Alignment.ALIGN_NORMAL, 1, 0, true); + float max = 0; + for (int i = 0; i < l.getLineCount(); i++) { + max = Math.max(max, l.getLineWidth(i)); + } + + /* + * Now set the popup size to be big enough for the text plus the border capped + * to DEFAULT_MAX_POPUP_WIDTH + */ + pop.setWidth(wid + (int) Math.ceil(max)); + pop.setHeight(ht + l.getHeight()); + } + + void setFrame() { + if (mErrorPopup != null) { + TextView tv = (TextView) mErrorPopup.getContentView(); + chooseSize(mErrorPopup, mError, tv); + mErrorPopup.update(mTextView, getErrorX(), getErrorY(), + mErrorPopup.getWidth(), mErrorPopup.getHeight()); + } + } + + /** + * Unlike {@link TextView#textCanBeSelected()}, this method is based on the <i>current</i> state + * of the TextView. textCanBeSelected() has to be true (this is one of the conditions to have + * a selection controller (see {@link #prepareCursorControllers()}), but this is not sufficient. + */ + private boolean canSelectText() { + return hasSelectionController() && mTextView.getText().length() != 0; + } + + /** + * It would be better to rely on the input type for everything. A password inputType should have + * a password transformation. We should hence use isPasswordInputType instead of this method. + * + * We should: + * - Call setInputType in setKeyListener instead of changing the input type directly (which + * would install the correct transformation). + * - Refuse the installation of a non-password transformation in setTransformation if the input + * type is password. + * + * However, this is like this for legacy reasons and we cannot break existing apps. This method + * is useful since it matches what the user can see (obfuscated text or not). + * + * @return true if the current transformation method is of the password type. + */ + private boolean hasPasswordTransformationMethod() { + return mTextView.getTransformationMethod() instanceof PasswordTransformationMethod; + } + + /** + * Adjusts selection to the word under last touch offset. + * Return true if the operation was successfully performed. + */ + private boolean selectCurrentWord() { + if (!canSelectText()) { + return false; + } + + if (hasPasswordTransformationMethod()) { + // Always select all on a password field. + // Cut/copy menu entries are not available for passwords, but being able to select all + // is however useful to delete or paste to replace the entire content. + return mTextView.selectAllText(); + } + + int inputType = mTextView.getInputType(); + int klass = inputType & InputType.TYPE_MASK_CLASS; + int variation = inputType & InputType.TYPE_MASK_VARIATION; + + // Specific text field types: select the entire text for these + if (klass == InputType.TYPE_CLASS_NUMBER || + klass == InputType.TYPE_CLASS_PHONE || + klass == InputType.TYPE_CLASS_DATETIME || + variation == InputType.TYPE_TEXT_VARIATION_URI || + variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS || + variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS || + variation == InputType.TYPE_TEXT_VARIATION_FILTER) { + return mTextView.selectAllText(); + } + + long lastTouchOffsets = getLastTouchOffsets(); + final int minOffset = TextUtils.unpackRangeStartFromLong(lastTouchOffsets); + final int maxOffset = TextUtils.unpackRangeEndFromLong(lastTouchOffsets); + + // Safety check in case standard touch event handling has been bypassed + if (minOffset < 0 || minOffset >= mTextView.getText().length()) return false; + if (maxOffset < 0 || maxOffset >= mTextView.getText().length()) return false; + + int selectionStart, selectionEnd; + + // If a URLSpan (web address, email, phone...) is found at that position, select it. + URLSpan[] urlSpans = ((Spanned) mTextView.getText()). + getSpans(minOffset, maxOffset, URLSpan.class); + if (urlSpans.length >= 1) { + URLSpan urlSpan = urlSpans[0]; + selectionStart = ((Spanned) mTextView.getText()).getSpanStart(urlSpan); + selectionEnd = ((Spanned) mTextView.getText()).getSpanEnd(urlSpan); + } else { + final WordIterator wordIterator = getWordIterator(); + wordIterator.setCharSequence(mTextView.getText(), minOffset, maxOffset); + + selectionStart = wordIterator.getBeginning(minOffset); + selectionEnd = wordIterator.getEnd(maxOffset); + + if (selectionStart == BreakIterator.DONE || selectionEnd == BreakIterator.DONE || + selectionStart == selectionEnd) { + // Possible when the word iterator does not properly handle the text's language + long range = getCharRange(minOffset); + selectionStart = TextUtils.unpackRangeStartFromLong(range); + selectionEnd = TextUtils.unpackRangeEndFromLong(range); + } + } + + Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd); + return selectionEnd > selectionStart; + } + + void onLocaleChanged() { + // Will be re-created on demand in getWordIterator with the proper new locale + mWordIterator = null; + } + + /** + * @hide + */ + public WordIterator getWordIterator() { + if (mWordIterator == null) { + mWordIterator = new WordIterator(mTextView.getTextServicesLocale()); + } + return mWordIterator; + } + + private long getCharRange(int offset) { + final int textLength = mTextView.getText().length(); + if (offset + 1 < textLength) { + final char currentChar = mTextView.getText().charAt(offset); + final char nextChar = mTextView.getText().charAt(offset + 1); + if (Character.isSurrogatePair(currentChar, nextChar)) { + return TextUtils.packRangeInLong(offset, offset + 2); + } + } + if (offset < textLength) { + return TextUtils.packRangeInLong(offset, offset + 1); + } + if (offset - 2 >= 0) { + final char previousChar = mTextView.getText().charAt(offset - 1); + final char previousPreviousChar = mTextView.getText().charAt(offset - 2); + if (Character.isSurrogatePair(previousPreviousChar, previousChar)) { + return TextUtils.packRangeInLong(offset - 2, offset); + } + } + if (offset - 1 >= 0) { + return TextUtils.packRangeInLong(offset - 1, offset); + } + return TextUtils.packRangeInLong(offset, offset); + } + + private boolean touchPositionIsInSelection() { + int selectionStart = mTextView.getSelectionStart(); + int selectionEnd = mTextView.getSelectionEnd(); + + if (selectionStart == selectionEnd) { + return false; + } + + if (selectionStart > selectionEnd) { + int tmp = selectionStart; + selectionStart = selectionEnd; + selectionEnd = tmp; + Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd); + } + + SelectionModifierCursorController selectionController = getSelectionController(); + int minOffset = selectionController.getMinTouchOffset(); + int maxOffset = selectionController.getMaxTouchOffset(); + + return ((minOffset >= selectionStart) && (maxOffset < selectionEnd)); + } + + private PositionListener getPositionListener() { + if (mPositionListener == null) { + mPositionListener = new PositionListener(); + } + return mPositionListener; + } + + private interface TextViewPositionListener { + public void updatePosition(int parentPositionX, int parentPositionY, + boolean parentPositionChanged, boolean parentScrolled); + } + + private boolean isPositionVisible(int positionX, int positionY) { + synchronized (TEMP_POSITION) { + final float[] position = TEMP_POSITION; + position[0] = positionX; + position[1] = positionY; + View view = mTextView; + + while (view != null) { + if (view != mTextView) { + // Local scroll is already taken into account in positionX/Y + position[0] -= view.getScrollX(); + position[1] -= view.getScrollY(); + } + + if (position[0] < 0 || position[1] < 0 || + position[0] > view.getWidth() || position[1] > view.getHeight()) { + return false; + } + + if (!view.getMatrix().isIdentity()) { + view.getMatrix().mapPoints(position); + } + + position[0] += view.getLeft(); + position[1] += view.getTop(); + + final ViewParent parent = view.getParent(); + if (parent instanceof View) { + view = (View) parent; + } else { + // We've reached the ViewRoot, stop iterating + view = null; + } + } + } + + // We've been able to walk up the view hierarchy and the position was never clipped + return true; + } + + private boolean isOffsetVisible(int offset) { + Layout layout = mTextView.getLayout(); + final int line = layout.getLineForOffset(offset); + final int lineBottom = layout.getLineBottom(line); + final int primaryHorizontal = (int) layout.getPrimaryHorizontal(offset); + return isPositionVisible(primaryHorizontal + mTextView.viewportToContentHorizontalOffset(), + lineBottom + mTextView.viewportToContentVerticalOffset()); + } + + /** Returns true if the screen coordinates position (x,y) corresponds to a character displayed + * in the view. Returns false when the position is in the empty space of left/right of text. + */ + private boolean isPositionOnText(float x, float y) { + Layout layout = mTextView.getLayout(); + if (layout == null) return false; + + final int line = mTextView.getLineAtCoordinate(y); + x = mTextView.convertToLocalHorizontalCoordinate(x); + + if (x < layout.getLineLeft(line)) return false; + if (x > layout.getLineRight(line)) return false; + return true; + } + + public boolean performLongClick(boolean handled) { + // Long press in empty space moves cursor and shows the Paste affordance if available. + if (!handled && !isPositionOnText(mLastDownPositionX, mLastDownPositionY) && + mInsertionControllerEnabled) { + final int offset = mTextView.getOffsetForPosition(mLastDownPositionX, + mLastDownPositionY); + stopSelectionActionMode(); + Selection.setSelection((Spannable) mTextView.getText(), offset); + getInsertionController().showWithActionPopup(); + handled = true; + } + + if (!handled && mSelectionActionMode != null) { + if (touchPositionIsInSelection()) { + // Start a drag + final int start = mTextView.getSelectionStart(); + final int end = mTextView.getSelectionEnd(); + CharSequence selectedText = mTextView.getTransformedText(start, end); + ClipData data = ClipData.newPlainText(null, selectedText); + DragLocalState localState = new DragLocalState(mTextView, start, end); + mTextView.startDrag(data, getTextThumbnailBuilder(selectedText), localState, 0); + stopSelectionActionMode(); + } else { + getSelectionController().hide(); + selectCurrentWord(); + getSelectionController().show(); + } + handled = true; + } + + // Start a new selection + if (!handled) { + handled = startSelectionActionMode(); + } + + return handled; + } + + private long getLastTouchOffsets() { + SelectionModifierCursorController selectionController = getSelectionController(); + final int minOffset = selectionController.getMinTouchOffset(); + final int maxOffset = selectionController.getMaxTouchOffset(); + return TextUtils.packRangeInLong(minOffset, maxOffset); + } + + void onFocusChanged(boolean focused, int direction) { + mShowCursor = SystemClock.uptimeMillis(); + ensureEndedBatchEdit(); + + if (focused) { + int selStart = mTextView.getSelectionStart(); + int selEnd = mTextView.getSelectionEnd(); + + // SelectAllOnFocus fields are highlighted and not selected. Do not start text selection + // mode for these, unless there was a specific selection already started. + final boolean isFocusHighlighted = mSelectAllOnFocus && selStart == 0 && + selEnd == mTextView.getText().length(); + + mCreatedWithASelection = mFrozenWithFocus && mTextView.hasSelection() && + !isFocusHighlighted; + + if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) { + // If a tap was used to give focus to that view, move cursor at tap position. + // Has to be done before onTakeFocus, which can be overloaded. + final int lastTapPosition = getLastTapPosition(); + if (lastTapPosition >= 0) { + Selection.setSelection((Spannable) mTextView.getText(), lastTapPosition); + } + + // Note this may have to be moved out of the Editor class + MovementMethod mMovement = mTextView.getMovementMethod(); + if (mMovement != null) { + mMovement.onTakeFocus(mTextView, (Spannable) mTextView.getText(), direction); + } + + // The DecorView does not have focus when the 'Done' ExtractEditText button is + // pressed. Since it is the ViewAncestor's mView, it requests focus before + // ExtractEditText clears focus, which gives focus to the ExtractEditText. + // This special case ensure that we keep current selection in that case. + // It would be better to know why the DecorView does not have focus at that time. + if (((mTextView instanceof ExtractEditText) || mSelectionMoved) && + selStart >= 0 && selEnd >= 0) { + /* + * Someone intentionally set the selection, so let them + * do whatever it is that they wanted to do instead of + * the default on-focus behavior. We reset the selection + * here instead of just skipping the onTakeFocus() call + * because some movement methods do something other than + * just setting the selection in theirs and we still + * need to go through that path. + */ + Selection.setSelection((Spannable) mTextView.getText(), selStart, selEnd); + } + + if (mSelectAllOnFocus) { + mTextView.selectAllText(); + } + + mTouchFocusSelected = true; + } + + mFrozenWithFocus = false; + mSelectionMoved = false; + + if (mError != null) { + showError(); + } + + makeBlink(); + } else { + if (mError != null) { + hideError(); + } + // Don't leave us in the middle of a batch edit. + mTextView.onEndBatchEdit(); + + if (mTextView instanceof ExtractEditText) { + // terminateTextSelectionMode removes selection, which we want to keep when + // ExtractEditText goes out of focus. + final int selStart = mTextView.getSelectionStart(); + final int selEnd = mTextView.getSelectionEnd(); + hideControllers(); + Selection.setSelection((Spannable) mTextView.getText(), selStart, selEnd); + } else { + hideControllers(); + downgradeEasyCorrectionSpans(); + } + + // No need to create the controller + if (mSelectionModifierCursorController != null) { + mSelectionModifierCursorController.resetTouchOffsets(); + } + } + } + + /** + * Downgrades to simple suggestions all the easy correction spans that are not a spell check + * span. + */ + private void downgradeEasyCorrectionSpans() { + CharSequence text = mTextView.getText(); + if (text instanceof Spannable) { + Spannable spannable = (Spannable) text; + SuggestionSpan[] suggestionSpans = spannable.getSpans(0, + spannable.length(), SuggestionSpan.class); + for (int i = 0; i < suggestionSpans.length; i++) { + int flags = suggestionSpans[i].getFlags(); + if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 + && (flags & SuggestionSpan.FLAG_MISSPELLED) == 0) { + flags &= ~SuggestionSpan.FLAG_EASY_CORRECT; + suggestionSpans[i].setFlags(flags); + } + } + } + } + + void sendOnTextChanged(int start, int after) { + updateSpellCheckSpans(start, start + after, false); + + // Hide the controllers as soon as text is modified (typing, procedural...) + // We do not hide the span controllers, since they can be added when a new text is + // inserted into the text view (voice IME). + hideCursorControllers(); + } + + private int getLastTapPosition() { + // No need to create the controller at that point, no last tap position saved + if (mSelectionModifierCursorController != null) { + int lastTapPosition = mSelectionModifierCursorController.getMinTouchOffset(); + if (lastTapPosition >= 0) { + // Safety check, should not be possible. + if (lastTapPosition > mTextView.getText().length()) { + lastTapPosition = mTextView.getText().length(); + } + return lastTapPosition; + } + } + + return -1; + } + + void onWindowFocusChanged(boolean hasWindowFocus) { + if (hasWindowFocus) { + if (mBlink != null) { + mBlink.uncancel(); + makeBlink(); + } + } else { + if (mBlink != null) { + mBlink.cancel(); + } + if (mInputContentType != null) { + mInputContentType.enterDown = false; + } + // Order matters! Must be done before onParentLostFocus to rely on isShowingUp + hideControllers(); + if (mSuggestionsPopupWindow != null) { + mSuggestionsPopupWindow.onParentLostFocus(); + } + + // Don't leave us in the middle of a batch edit. + mTextView.onEndBatchEdit(); + } + } + + void onTouchEvent(MotionEvent event) { + if (hasSelectionController()) { + getSelectionController().onTouchEvent(event); + } + + if (mShowSuggestionRunnable != null) { + mTextView.removeCallbacks(mShowSuggestionRunnable); + mShowSuggestionRunnable = null; + } + + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mLastDownPositionX = event.getX(); + mLastDownPositionY = event.getY(); + + // Reset this state; it will be re-set if super.onTouchEvent + // causes focus to move to the view. + mTouchFocusSelected = false; + mIgnoreActionUpEvent = false; + } + } + + public void beginBatchEdit() { + mInBatchEditControllers = true; + final InputMethodState ims = mInputMethodState; + if (ims != null) { + int nesting = ++ims.mBatchEditNesting; + if (nesting == 1) { + ims.mCursorChanged = false; + ims.mChangedDelta = 0; + if (ims.mContentChanged) { + // We already have a pending change from somewhere else, + // so turn this into a full update. + ims.mChangedStart = 0; + ims.mChangedEnd = mTextView.getText().length(); + } else { + ims.mChangedStart = EXTRACT_UNKNOWN; + ims.mChangedEnd = EXTRACT_UNKNOWN; + ims.mContentChanged = false; + } + mTextView.onBeginBatchEdit(); + } + } + } + + public void endBatchEdit() { + mInBatchEditControllers = false; + final InputMethodState ims = mInputMethodState; + if (ims != null) { + int nesting = --ims.mBatchEditNesting; + if (nesting == 0) { + finishBatchEdit(ims); + } + } + } + + void ensureEndedBatchEdit() { + final InputMethodState ims = mInputMethodState; + if (ims != null && ims.mBatchEditNesting != 0) { + ims.mBatchEditNesting = 0; + finishBatchEdit(ims); + } + } + + void finishBatchEdit(final InputMethodState ims) { + mTextView.onEndBatchEdit(); + + if (ims.mContentChanged || ims.mSelectionModeChanged) { + mTextView.updateAfterEdit(); + reportExtractedText(); + } else if (ims.mCursorChanged) { + // Cheezy way to get us to report the current cursor location. + mTextView.invalidateCursor(); + } + } + + static final int EXTRACT_NOTHING = -2; + static final int EXTRACT_UNKNOWN = -1; + + boolean extractText(ExtractedTextRequest request, ExtractedText outText) { + return extractTextInternal(request, EXTRACT_UNKNOWN, EXTRACT_UNKNOWN, + EXTRACT_UNKNOWN, outText); + } + + private boolean extractTextInternal(ExtractedTextRequest request, + int partialStartOffset, int partialEndOffset, int delta, + ExtractedText outText) { + final CharSequence content = mTextView.getText(); + if (content != null) { + if (partialStartOffset != EXTRACT_NOTHING) { + final int N = content.length(); + if (partialStartOffset < 0) { + outText.partialStartOffset = outText.partialEndOffset = -1; + partialStartOffset = 0; + partialEndOffset = N; + } else { + // Now use the delta to determine the actual amount of text + // we need. + partialEndOffset += delta; + // Adjust offsets to ensure we contain full spans. + if (content instanceof Spanned) { + Spanned spanned = (Spanned)content; + Object[] spans = spanned.getSpans(partialStartOffset, + partialEndOffset, ParcelableSpan.class); + int i = spans.length; + while (i > 0) { + i--; + int j = spanned.getSpanStart(spans[i]); + if (j < partialStartOffset) partialStartOffset = j; + j = spanned.getSpanEnd(spans[i]); + if (j > partialEndOffset) partialEndOffset = j; + } + } + outText.partialStartOffset = partialStartOffset; + outText.partialEndOffset = partialEndOffset - delta; + + if (partialStartOffset > N) { + partialStartOffset = N; + } else if (partialStartOffset < 0) { + partialStartOffset = 0; + } + if (partialEndOffset > N) { + partialEndOffset = N; + } else if (partialEndOffset < 0) { + partialEndOffset = 0; + } + } + if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) { + outText.text = content.subSequence(partialStartOffset, + partialEndOffset); + } else { + outText.text = TextUtils.substring(content, partialStartOffset, + partialEndOffset); + } + } else { + outText.partialStartOffset = 0; + outText.partialEndOffset = 0; + outText.text = ""; + } + outText.flags = 0; + if (MetaKeyKeyListener.getMetaState(content, MetaKeyKeyListener.META_SELECTING) != 0) { + outText.flags |= ExtractedText.FLAG_SELECTING; + } + if (mTextView.isSingleLine()) { + outText.flags |= ExtractedText.FLAG_SINGLE_LINE; + } + outText.startOffset = 0; + outText.selectionStart = mTextView.getSelectionStart(); + outText.selectionEnd = mTextView.getSelectionEnd(); + return true; + } + return false; + } + + boolean reportExtractedText() { + final Editor.InputMethodState ims = mInputMethodState; + if (ims != null) { + final boolean contentChanged = ims.mContentChanged; + if (contentChanged || ims.mSelectionModeChanged) { + ims.mContentChanged = false; + ims.mSelectionModeChanged = false; + final ExtractedTextRequest req = ims.mExtracting; + if (req != null) { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + if (TextView.DEBUG_EXTRACT) Log.v(TextView.LOG_TAG, + "Retrieving extracted start=" + ims.mChangedStart + + " end=" + ims.mChangedEnd + + " delta=" + ims.mChangedDelta); + if (ims.mChangedStart < 0 && !contentChanged) { + ims.mChangedStart = EXTRACT_NOTHING; + } + if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd, + ims.mChangedDelta, ims.mTmpExtracted)) { + if (TextView.DEBUG_EXTRACT) Log.v(TextView.LOG_TAG, + "Reporting extracted start=" + + ims.mTmpExtracted.partialStartOffset + + " end=" + ims.mTmpExtracted.partialEndOffset + + ": " + ims.mTmpExtracted.text); + imm.updateExtractedText(mTextView, req.token, ims.mTmpExtracted); + ims.mChangedStart = EXTRACT_UNKNOWN; + ims.mChangedEnd = EXTRACT_UNKNOWN; + ims.mChangedDelta = 0; + ims.mContentChanged = false; + return true; + } + } + } + } + } + return false; + } + + void onDraw(Canvas canvas, Layout layout, Path highlight, Paint highlightPaint, + int cursorOffsetVertical) { + final int selectionStart = mTextView.getSelectionStart(); + final int selectionEnd = mTextView.getSelectionEnd(); + + final InputMethodState ims = mInputMethodState; + if (ims != null && ims.mBatchEditNesting == 0) { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + if (imm.isActive(mTextView)) { + boolean reported = false; + if (ims.mContentChanged || ims.mSelectionModeChanged) { + // We are in extract mode and the content has changed + // in some way... just report complete new text to the + // input method. + reported = reportExtractedText(); + } + if (!reported && highlight != null) { + int candStart = -1; + int candEnd = -1; + if (mTextView.getText() instanceof Spannable) { + Spannable sp = (Spannable) mTextView.getText(); + candStart = EditableInputConnection.getComposingSpanStart(sp); + candEnd = EditableInputConnection.getComposingSpanEnd(sp); + } + imm.updateSelection(mTextView, + selectionStart, selectionEnd, candStart, candEnd); + } + } + + if (imm.isWatchingCursor(mTextView) && highlight != null) { + highlight.computeBounds(ims.mTmpRectF, true); + ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0; + + canvas.getMatrix().mapPoints(ims.mTmpOffset); + ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]); + + ims.mTmpRectF.offset(0, cursorOffsetVertical); + + ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5), + (int)(ims.mTmpRectF.top + 0.5), + (int)(ims.mTmpRectF.right + 0.5), + (int)(ims.mTmpRectF.bottom + 0.5)); + + imm.updateCursor(mTextView, + ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top, + ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom); + } + } + } + + if (mCorrectionHighlighter != null) { + mCorrectionHighlighter.draw(canvas, cursorOffsetVertical); + } + + if (highlight != null && selectionStart == selectionEnd && mCursorCount > 0) { + drawCursor(canvas, cursorOffsetVertical); + // Rely on the drawable entirely, do not draw the cursor line. + // Has to be done after the IMM related code above which relies on the highlight. + highlight = null; + } + + if (mTextView.canHaveDisplayList() && canvas.isHardwareAccelerated()) { + drawHardwareAccelerated(canvas, layout, highlight, highlightPaint, + cursorOffsetVertical); + } else { + layout.draw(canvas, highlight, highlightPaint, cursorOffsetVertical); + } + } + + private void drawHardwareAccelerated(Canvas canvas, Layout layout, Path highlight, + Paint highlightPaint, int cursorOffsetVertical) { + final int width = mTextView.getWidth(); + final int height = mTextView.getHeight(); + + final long lineRange = layout.getLineRangeForDraw(canvas); + int firstLine = TextUtils.unpackRangeStartFromLong(lineRange); + int lastLine = TextUtils.unpackRangeEndFromLong(lineRange); + if (lastLine < 0) return; + + layout.drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical, + firstLine, lastLine); + + if (layout instanceof DynamicLayout) { + if (mTextDisplayLists == null) { + mTextDisplayLists = new DisplayList[ArrayUtils.idealObjectArraySize(0)]; + } + + DynamicLayout dynamicLayout = (DynamicLayout) layout; + int[] blockEnds = dynamicLayout.getBlockEnds(); + int[] blockIndices = dynamicLayout.getBlockIndices(); + final int numberOfBlocks = dynamicLayout.getNumberOfBlocks(); + + final int mScrollX = mTextView.getScrollX(); + final int mScrollY = mTextView.getScrollY(); + canvas.translate(mScrollX, mScrollY); + int endOfPreviousBlock = -1; + int searchStartIndex = 0; + for (int i = 0; i < numberOfBlocks; i++) { + int blockEnd = blockEnds[i]; + int blockIndex = blockIndices[i]; + + final boolean blockIsInvalid = blockIndex == DynamicLayout.INVALID_BLOCK_INDEX; + if (blockIsInvalid) { + blockIndex = getAvailableDisplayListIndex(blockIndices, numberOfBlocks, + searchStartIndex); + // Dynamic layout internal block indices structure is updated from Editor + blockIndices[i] = blockIndex; + searchStartIndex = blockIndex + 1; + } + + DisplayList blockDisplayList = mTextDisplayLists[blockIndex]; + if (blockDisplayList == null) { + blockDisplayList = mTextDisplayLists[blockIndex] = + mTextView.getHardwareRenderer().createDisplayList("Text " + blockIndex); + } else { + if (blockIsInvalid) blockDisplayList.invalidate(); + } + + if (!blockDisplayList.isValid()) { + final HardwareCanvas hardwareCanvas = blockDisplayList.start(); + try { + hardwareCanvas.setViewport(width, height); + // The dirty rect should always be null for a display list + hardwareCanvas.onPreDraw(null); + hardwareCanvas.translate(-mScrollX, -mScrollY); + layout.drawText(hardwareCanvas, endOfPreviousBlock + 1, blockEnd); + hardwareCanvas.translate(mScrollX, mScrollY); + } finally { + hardwareCanvas.onPostDraw(); + blockDisplayList.end(); + if (View.USE_DISPLAY_LIST_PROPERTIES) { + blockDisplayList.setLeftTopRightBottom(0, 0, width, height); + } + } + } + + ((HardwareCanvas) canvas).drawDisplayList(blockDisplayList, width, height, null, + DisplayList.FLAG_CLIP_CHILDREN); + endOfPreviousBlock = blockEnd; + } + canvas.translate(-mScrollX, -mScrollY); + } else { + // Boring layout is used for empty and hint text + layout.drawText(canvas, firstLine, lastLine); + } + } + + private int getAvailableDisplayListIndex(int[] blockIndices, int numberOfBlocks, + int searchStartIndex) { + int length = mTextDisplayLists.length; + for (int i = searchStartIndex; i < length; i++) { + boolean blockIndexFound = false; + for (int j = 0; j < numberOfBlocks; j++) { + if (blockIndices[j] == i) { + blockIndexFound = true; + break; + } + } + if (blockIndexFound) continue; + return i; + } + + // No available index found, the pool has to grow + int newSize = ArrayUtils.idealIntArraySize(length + 1); + DisplayList[] displayLists = new DisplayList[newSize]; + System.arraycopy(mTextDisplayLists, 0, displayLists, 0, length); + mTextDisplayLists = displayLists; + return length; + } + + private void drawCursor(Canvas canvas, int cursorOffsetVertical) { + final boolean translate = cursorOffsetVertical != 0; + if (translate) canvas.translate(0, cursorOffsetVertical); + for (int i = 0; i < mCursorCount; i++) { + mCursorDrawable[i].draw(canvas); + } + if (translate) canvas.translate(0, -cursorOffsetVertical); + } + + void invalidateTextDisplayList() { + if (mTextDisplayLists != null) { + for (int i = 0; i < mTextDisplayLists.length; i++) { + if (mTextDisplayLists[i] != null) mTextDisplayLists[i].invalidate(); + } + } + } + + void updateCursorsPositions() { + if (mTextView.mCursorDrawableRes == 0) { + mCursorCount = 0; + return; + } + + Layout layout = mTextView.getLayout(); + final int offset = mTextView.getSelectionStart(); + final int line = layout.getLineForOffset(offset); + final int top = layout.getLineTop(line); + final int bottom = layout.getLineTop(line + 1); + + mCursorCount = layout.isLevelBoundary(offset) ? 2 : 1; + + int middle = bottom; + if (mCursorCount == 2) { + // Similar to what is done in {@link Layout.#getCursorPath(int, Path, CharSequence)} + middle = (top + bottom) >> 1; + } + + updateCursorPosition(0, top, middle, layout.getPrimaryHorizontal(offset)); + + if (mCursorCount == 2) { + updateCursorPosition(1, middle, bottom, layout.getSecondaryHorizontal(offset)); + } + } + + /** + * @return true if the selection mode was actually started. + */ + boolean startSelectionActionMode() { + if (mSelectionActionMode != null) { + // Selection action mode is already started + return false; + } + + if (!canSelectText() || !mTextView.requestFocus()) { + Log.w(TextView.LOG_TAG, + "TextView does not support text selection. Action mode cancelled."); + return false; + } + + if (!mTextView.hasSelection()) { + // There may already be a selection on device rotation + if (!selectCurrentWord()) { + // No word found under cursor or text selection not permitted. + return false; + } + } + + boolean willExtract = extractedTextModeWillBeStarted(); + + // Do not start the action mode when extracted text will show up full screen, which would + // immediately hide the newly created action bar and would be visually distracting. + if (!willExtract) { + ActionMode.Callback actionModeCallback = new SelectionActionModeCallback(); + mSelectionActionMode = mTextView.startActionMode(actionModeCallback); + } + + final boolean selectionStarted = mSelectionActionMode != null || willExtract; + if (selectionStarted && !mTextView.isTextSelectable()) { + // Show the IME to be able to replace text, except when selecting non editable text. + final InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + imm.showSoftInput(mTextView, 0, null); + } + } + + return selectionStarted; + } + + private boolean extractedTextModeWillBeStarted() { + if (!(mTextView instanceof ExtractEditText)) { + final InputMethodManager imm = InputMethodManager.peekInstance(); + return imm != null && imm.isFullscreenMode(); + } + return false; + } + + /** + * @return <code>true</code> if the cursor/current selection overlaps a {@link SuggestionSpan}. + */ + private boolean isCursorInsideSuggestionSpan() { + CharSequence text = mTextView.getText(); + if (!(text instanceof Spannable)) return false; + + SuggestionSpan[] suggestionSpans = ((Spannable) text).getSpans( + mTextView.getSelectionStart(), mTextView.getSelectionEnd(), SuggestionSpan.class); + return (suggestionSpans.length > 0); + } + + /** + * @return <code>true</code> if the cursor is inside an {@link SuggestionSpan} with + * {@link SuggestionSpan#FLAG_EASY_CORRECT} set. + */ + private boolean isCursorInsideEasyCorrectionSpan() { + Spannable spannable = (Spannable) mTextView.getText(); + SuggestionSpan[] suggestionSpans = spannable.getSpans(mTextView.getSelectionStart(), + mTextView.getSelectionEnd(), SuggestionSpan.class); + for (int i = 0; i < suggestionSpans.length; i++) { + if ((suggestionSpans[i].getFlags() & SuggestionSpan.FLAG_EASY_CORRECT) != 0) { + return true; + } + } + return false; + } + + void onTouchUpEvent(MotionEvent event) { + boolean selectAllGotFocus = mSelectAllOnFocus && mTextView.didTouchFocusSelect(); + hideControllers(); + CharSequence text = mTextView.getText(); + if (!selectAllGotFocus && text.length() > 0) { + // Move cursor + final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY()); + Selection.setSelection((Spannable) text, offset); + if (mSpellChecker != null) { + // When the cursor moves, the word that was typed may need spell check + mSpellChecker.onSelectionChanged(); + } + if (!extractedTextModeWillBeStarted()) { + if (isCursorInsideEasyCorrectionSpan()) { + mShowSuggestionRunnable = new Runnable() { + public void run() { + showSuggestions(); + } + }; + // removeCallbacks is performed on every touch + mTextView.postDelayed(mShowSuggestionRunnable, + ViewConfiguration.getDoubleTapTimeout()); + } else if (hasInsertionController()) { + getInsertionController().show(); + } + } + } + } + + protected void stopSelectionActionMode() { + if (mSelectionActionMode != null) { + // This will hide the mSelectionModifierCursorController + mSelectionActionMode.finish(); + } + } + + /** + * @return True if this view supports insertion handles. + */ + boolean hasInsertionController() { + return mInsertionControllerEnabled; + } + + /** + * @return True if this view supports selection handles. + */ + boolean hasSelectionController() { + return mSelectionControllerEnabled; + } + + InsertionPointCursorController getInsertionController() { + if (!mInsertionControllerEnabled) { + return null; + } + + if (mInsertionPointCursorController == null) { + mInsertionPointCursorController = new InsertionPointCursorController(); + + final ViewTreeObserver observer = mTextView.getViewTreeObserver(); + observer.addOnTouchModeChangeListener(mInsertionPointCursorController); + } + + return mInsertionPointCursorController; + } + + SelectionModifierCursorController getSelectionController() { + if (!mSelectionControllerEnabled) { + return null; + } + + if (mSelectionModifierCursorController == null) { + mSelectionModifierCursorController = new SelectionModifierCursorController(); + + final ViewTreeObserver observer = mTextView.getViewTreeObserver(); + observer.addOnTouchModeChangeListener(mSelectionModifierCursorController); + } + + return mSelectionModifierCursorController; + } + + private void updateCursorPosition(int cursorIndex, int top, int bottom, float horizontal) { + if (mCursorDrawable[cursorIndex] == null) + mCursorDrawable[cursorIndex] = mTextView.getResources().getDrawable( + mTextView.mCursorDrawableRes); + + if (mTempRect == null) mTempRect = new Rect(); + mCursorDrawable[cursorIndex].getPadding(mTempRect); + final int width = mCursorDrawable[cursorIndex].getIntrinsicWidth(); + horizontal = Math.max(0.5f, horizontal - 0.5f); + final int left = (int) (horizontal) - mTempRect.left; + mCursorDrawable[cursorIndex].setBounds(left, top - mTempRect.top, left + width, + bottom + mTempRect.bottom); + } + + /** + * Called by the framework in response to a text auto-correction (such as fixing a typo using a + * a dictionnary) from the current input method, provided by it calling + * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default + * implementation flashes the background of the corrected word to provide feedback to the user. + * + * @param info The auto correct info about the text that was corrected. + */ + public void onCommitCorrection(CorrectionInfo info) { + if (mCorrectionHighlighter == null) { + mCorrectionHighlighter = new CorrectionHighlighter(); + } else { + mCorrectionHighlighter.invalidate(false); + } + + mCorrectionHighlighter.highlight(info); + } + + void showSuggestions() { + if (mSuggestionsPopupWindow == null) { + mSuggestionsPopupWindow = new SuggestionsPopupWindow(); + } + hideControllers(); + mSuggestionsPopupWindow.show(); + } + + boolean areSuggestionsShown() { + return mSuggestionsPopupWindow != null && mSuggestionsPopupWindow.isShowing(); + } + + void onScrollChanged() { + if (mPositionListener != null) { + mPositionListener.onScrollChanged(); + } + // Internal scroll affects the clip boundaries + invalidateTextDisplayList(); + } + + /** + * @return True when the TextView isFocused and has a valid zero-length selection (cursor). + */ + private boolean shouldBlink() { + if (!isCursorVisible() || !mTextView.isFocused()) return false; + + final int start = mTextView.getSelectionStart(); + if (start < 0) return false; + + final int end = mTextView.getSelectionEnd(); + if (end < 0) return false; + + return start == end; + } + + void makeBlink() { + if (shouldBlink()) { + mShowCursor = SystemClock.uptimeMillis(); + if (mBlink == null) mBlink = new Blink(); + mBlink.removeCallbacks(mBlink); + mBlink.postAtTime(mBlink, mShowCursor + BLINK); + } else { + if (mBlink != null) mBlink.removeCallbacks(mBlink); + } + } + + private class Blink extends Handler implements Runnable { + private boolean mCancelled; + + public void run() { + Log.d("GILLES", "blinking !!!"); + if (mCancelled) { + return; + } + + removeCallbacks(Blink.this); + + if (shouldBlink()) { + if (mTextView.getLayout() != null) { + mTextView.invalidateCursorPath(); + } + + postAtTime(this, SystemClock.uptimeMillis() + BLINK); + } + } + + void cancel() { + if (!mCancelled) { + removeCallbacks(Blink.this); + mCancelled = true; + } + } + + void uncancel() { + mCancelled = false; + } + } + + private DragShadowBuilder getTextThumbnailBuilder(CharSequence text) { + TextView shadowView = (TextView) View.inflate(mTextView.getContext(), + com.android.internal.R.layout.text_drag_thumbnail, null); + + if (shadowView == null) { + throw new IllegalArgumentException("Unable to inflate text drag thumbnail"); + } + + if (text.length() > DRAG_SHADOW_MAX_TEXT_LENGTH) { + text = text.subSequence(0, DRAG_SHADOW_MAX_TEXT_LENGTH); + } + shadowView.setText(text); + shadowView.setTextColor(mTextView.getTextColors()); + + shadowView.setTextAppearance(mTextView.getContext(), R.styleable.Theme_textAppearanceLarge); + shadowView.setGravity(Gravity.CENTER); + + shadowView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + + final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + shadowView.measure(size, size); + + shadowView.layout(0, 0, shadowView.getMeasuredWidth(), shadowView.getMeasuredHeight()); + shadowView.invalidate(); + return new DragShadowBuilder(shadowView); + } + + private static class DragLocalState { + public TextView sourceTextView; + public int start, end; + + public DragLocalState(TextView sourceTextView, int start, int end) { + this.sourceTextView = sourceTextView; + this.start = start; + this.end = end; + } + } + + void onDrop(DragEvent event) { + StringBuilder content = new StringBuilder(""); + ClipData clipData = event.getClipData(); + final int itemCount = clipData.getItemCount(); + for (int i=0; i < itemCount; i++) { + Item item = clipData.getItemAt(i); + content.append(item.coerceToText(mTextView.getContext())); + } + + final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY()); + + Object localState = event.getLocalState(); + DragLocalState dragLocalState = null; + if (localState instanceof DragLocalState) { + dragLocalState = (DragLocalState) localState; + } + boolean dragDropIntoItself = dragLocalState != null && + dragLocalState.sourceTextView == mTextView; + + if (dragDropIntoItself) { + if (offset >= dragLocalState.start && offset < dragLocalState.end) { + // A drop inside the original selection discards the drop. + return; + } + } + + final int originalLength = mTextView.getText().length(); + long minMax = mTextView.prepareSpacesAroundPaste(offset, offset, content); + int min = TextUtils.unpackRangeStartFromLong(minMax); + int max = TextUtils.unpackRangeEndFromLong(minMax); + + Selection.setSelection((Spannable) mTextView.getText(), max); + mTextView.replaceText_internal(min, max, content); + + if (dragDropIntoItself) { + int dragSourceStart = dragLocalState.start; + int dragSourceEnd = dragLocalState.end; + if (max <= dragSourceStart) { + // Inserting text before selection has shifted positions + final int shift = mTextView.getText().length() - originalLength; + dragSourceStart += shift; + dragSourceEnd += shift; + } + + // Delete original selection + mTextView.deleteText_internal(dragSourceStart, dragSourceEnd); + + // Make sure we do not leave two adjacent spaces. + CharSequence t = mTextView.getTransformedText(dragSourceStart - 1, dragSourceStart + 1); + if ( (dragSourceStart == 0 || Character.isSpaceChar(t.charAt(0))) && + (dragSourceStart == mTextView.getText().length() || + Character.isSpaceChar(t.charAt(1))) ) { + final int pos = dragSourceStart == mTextView.getText().length() ? + dragSourceStart - 1 : dragSourceStart; + mTextView.deleteText_internal(pos, pos + 1); + } + } + } + + /** + * Controls the {@link EasyEditSpan} monitoring when it is added, and when the related + * pop-up should be displayed. + */ + class EasyEditSpanController implements TextWatcher { + + private static final int DISPLAY_TIMEOUT_MS = 3000; // 3 secs + + private EasyEditPopupWindow mPopupWindow; + + private EasyEditSpan mEasyEditSpan; + + private Runnable mHidePopup; + + public void hide() { + if (mPopupWindow != null) { + mPopupWindow.hide(); + mTextView.removeCallbacks(mHidePopup); + } + removeSpans(mTextView.getText()); + mEasyEditSpan = null; + } + + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // Intentionally empty + } + + public void afterTextChanged(Editable s) { + // Intentionally empty + } + + /** + * Monitors the changes in the text. + * + * <p>{@link SpanWatcher#onSpanAdded(Spannable, Object, int, int)} cannot be used, + * as the notifications are not sent when a spannable (with spans) is inserted. + */ + public void onTextChanged(CharSequence buffer, int start, int before, int after) { + adjustSpans(buffer, start, after); + + if (mTextView.getWindowVisibility() != View.VISIBLE) { + // The window is not visible yet, ignore the text change. + return; + } + + if (mTextView.getLayout() == null) { + // The view has not been layout yet, ignore the text change + return; + } + + InputMethodManager imm = InputMethodManager.peekInstance(); + if (!(mTextView instanceof ExtractEditText) && imm != null && imm.isFullscreenMode()) { + // The input is in extract mode. We do not have to handle the easy edit in the + // original TextView, as the ExtractEditText will do + return; + } + + // Remove the current easy edit span, as the text changed, and remove the pop-up + // (if any) + if (mEasyEditSpan != null) { + if (buffer instanceof Spannable) { + ((Spannable) buffer).removeSpan(mEasyEditSpan); + } + mEasyEditSpan = null; + } + if (mPopupWindow != null && mPopupWindow.isShowing()) { + mPopupWindow.hide(); + } + + // Display the new easy edit span (if any). + if (buffer instanceof Spanned) { + mEasyEditSpan = getSpan((Spanned) buffer); + if (mEasyEditSpan != null) { + if (mPopupWindow == null) { + mPopupWindow = new EasyEditPopupWindow(); + mHidePopup = new Runnable() { + @Override + public void run() { + hide(); + } + }; + } + mPopupWindow.show(mEasyEditSpan); + mTextView.removeCallbacks(mHidePopup); + mTextView.postDelayed(mHidePopup, DISPLAY_TIMEOUT_MS); + } + } + } + + /** + * Adjusts the spans by removing all of them except the last one. + */ + private void adjustSpans(CharSequence buffer, int start, int after) { + // This method enforces that only one easy edit span is attached to the text. + // A better way to enforce this would be to listen for onSpanAdded, but this method + // cannot be used in this scenario as no notification is triggered when a text with + // spans is inserted into a text. + if (buffer instanceof Spannable) { + Spannable spannable = (Spannable) buffer; + EasyEditSpan[] spans = spannable.getSpans(start, start + after, EasyEditSpan.class); + if (spans.length > 0) { + // Assuming there was only one EasyEditSpan before, we only need check to + // check for a duplicate if a new one is found in the modified interval + spans = spannable.getSpans(0, spannable.length(), EasyEditSpan.class); + for (int i = 1; i < spans.length; i++) { + spannable.removeSpan(spans[i]); + } + } + } + } + + /** + * Removes all the {@link EasyEditSpan} currently attached. + */ + private void removeSpans(CharSequence buffer) { + if (buffer instanceof Spannable) { + Spannable spannable = (Spannable) buffer; + EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(), + EasyEditSpan.class); + for (int i = 0; i < spans.length; i++) { + spannable.removeSpan(spans[i]); + } + } + } + + private EasyEditSpan getSpan(Spanned spanned) { + EasyEditSpan[] easyEditSpans = spanned.getSpans(0, spanned.length(), + EasyEditSpan.class); + if (easyEditSpans.length == 0) { + return null; + } else { + return easyEditSpans[0]; + } + } + } + + /** + * Displays the actions associated to an {@link EasyEditSpan}. The pop-up is controlled + * by {@link EasyEditSpanController}. + */ + private class EasyEditPopupWindow extends PinnedPopupWindow + implements OnClickListener { + private static final int POPUP_TEXT_LAYOUT = + com.android.internal.R.layout.text_edit_action_popup_text; + private TextView mDeleteTextView; + private EasyEditSpan mEasyEditSpan; + + @Override + protected void createPopupWindow() { + mPopupWindow = new PopupWindow(mTextView.getContext(), null, + com.android.internal.R.attr.textSelectHandleWindowStyle); + mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); + mPopupWindow.setClippingEnabled(true); + } + + @Override + protected void initContentView() { + LinearLayout linearLayout = new LinearLayout(mTextView.getContext()); + linearLayout.setOrientation(LinearLayout.HORIZONTAL); + mContentView = linearLayout; + mContentView.setBackgroundResource( + com.android.internal.R.drawable.text_edit_side_paste_window); + + LayoutInflater inflater = (LayoutInflater)mTextView.getContext(). + getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + LayoutParams wrapContent = new LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + + mDeleteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null); + mDeleteTextView.setLayoutParams(wrapContent); + mDeleteTextView.setText(com.android.internal.R.string.delete); + mDeleteTextView.setOnClickListener(this); + mContentView.addView(mDeleteTextView); + } + + public void show(EasyEditSpan easyEditSpan) { + mEasyEditSpan = easyEditSpan; + super.show(); + } + + @Override + public void onClick(View view) { + if (view == mDeleteTextView) { + Editable editable = (Editable) mTextView.getText(); + int start = editable.getSpanStart(mEasyEditSpan); + int end = editable.getSpanEnd(mEasyEditSpan); + if (start >= 0 && end >= 0) { + mTextView.deleteText_internal(start, end); + } + } + } + + @Override + protected int getTextOffset() { + // Place the pop-up at the end of the span + Editable editable = (Editable) mTextView.getText(); + return editable.getSpanEnd(mEasyEditSpan); + } + + @Override + protected int getVerticalLocalPosition(int line) { + return mTextView.getLayout().getLineBottom(line); + } + + @Override + protected int clipVertically(int positionY) { + // As we display the pop-up below the span, no vertical clipping is required. + return positionY; + } + } + + private class PositionListener implements ViewTreeObserver.OnPreDrawListener { + // 3 handles + // 3 ActionPopup [replace, suggestion, easyedit] (suggestionsPopup first hides the others) + private final int MAXIMUM_NUMBER_OF_LISTENERS = 6; + private TextViewPositionListener[] mPositionListeners = + new TextViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS]; + private boolean mCanMove[] = new boolean[MAXIMUM_NUMBER_OF_LISTENERS]; + private boolean mPositionHasChanged = true; + // Absolute position of the TextView with respect to its parent window + private int mPositionX, mPositionY; + private int mNumberOfListeners; + private boolean mScrollHasChanged; + final int[] mTempCoords = new int[2]; + + public void addSubscriber(TextViewPositionListener positionListener, boolean canMove) { + if (mNumberOfListeners == 0) { + updatePosition(); + ViewTreeObserver vto = mTextView.getViewTreeObserver(); + vto.addOnPreDrawListener(this); + } + + int emptySlotIndex = -1; + for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) { + TextViewPositionListener listener = mPositionListeners[i]; + if (listener == positionListener) { + return; + } else if (emptySlotIndex < 0 && listener == null) { + emptySlotIndex = i; + } + } + + mPositionListeners[emptySlotIndex] = positionListener; + mCanMove[emptySlotIndex] = canMove; + mNumberOfListeners++; + } + + public void removeSubscriber(TextViewPositionListener positionListener) { + for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) { + if (mPositionListeners[i] == positionListener) { + mPositionListeners[i] = null; + mNumberOfListeners--; + break; + } + } + + if (mNumberOfListeners == 0) { + ViewTreeObserver vto = mTextView.getViewTreeObserver(); + vto.removeOnPreDrawListener(this); + } + } + + public int getPositionX() { + return mPositionX; + } + + public int getPositionY() { + return mPositionY; + } + + @Override + public boolean onPreDraw() { + updatePosition(); + + for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) { + if (mPositionHasChanged || mScrollHasChanged || mCanMove[i]) { + TextViewPositionListener positionListener = mPositionListeners[i]; + if (positionListener != null) { + positionListener.updatePosition(mPositionX, mPositionY, + mPositionHasChanged, mScrollHasChanged); + } + } + } + + mScrollHasChanged = false; + return true; + } + + private void updatePosition() { + mTextView.getLocationInWindow(mTempCoords); + + mPositionHasChanged = mTempCoords[0] != mPositionX || mTempCoords[1] != mPositionY; + + mPositionX = mTempCoords[0]; + mPositionY = mTempCoords[1]; + } + + public void onScrollChanged() { + mScrollHasChanged = true; + } + } + + private abstract class PinnedPopupWindow implements TextViewPositionListener { + protected PopupWindow mPopupWindow; + protected ViewGroup mContentView; + int mPositionX, mPositionY; + + protected abstract void createPopupWindow(); + protected abstract void initContentView(); + protected abstract int getTextOffset(); + protected abstract int getVerticalLocalPosition(int line); + protected abstract int clipVertically(int positionY); + + public PinnedPopupWindow() { + createPopupWindow(); + + mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL); + mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT); + mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); + + initContentView(); + + LayoutParams wrapContent = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + mContentView.setLayoutParams(wrapContent); + + mPopupWindow.setContentView(mContentView); + } + + public void show() { + getPositionListener().addSubscriber(this, false /* offset is fixed */); + + computeLocalPosition(); + + final PositionListener positionListener = getPositionListener(); + updatePosition(positionListener.getPositionX(), positionListener.getPositionY()); + } + + protected void measureContent() { + final DisplayMetrics displayMetrics = mTextView.getResources().getDisplayMetrics(); + mContentView.measure( + View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels, + View.MeasureSpec.AT_MOST), + View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels, + View.MeasureSpec.AT_MOST)); + } + + /* The popup window will be horizontally centered on the getTextOffset() and vertically + * positioned according to viewportToContentHorizontalOffset. + * + * This method assumes that mContentView has properly been measured from its content. */ + private void computeLocalPosition() { + measureContent(); + final int width = mContentView.getMeasuredWidth(); + final int offset = getTextOffset(); + mPositionX = (int) (mTextView.getLayout().getPrimaryHorizontal(offset) - width / 2.0f); + mPositionX += mTextView.viewportToContentHorizontalOffset(); + + final int line = mTextView.getLayout().getLineForOffset(offset); + mPositionY = getVerticalLocalPosition(line); + mPositionY += mTextView.viewportToContentVerticalOffset(); + } + + private void updatePosition(int parentPositionX, int parentPositionY) { + int positionX = parentPositionX + mPositionX; + int positionY = parentPositionY + mPositionY; + + positionY = clipVertically(positionY); + + // Horizontal clipping + final DisplayMetrics displayMetrics = mTextView.getResources().getDisplayMetrics(); + final int width = mContentView.getMeasuredWidth(); + positionX = Math.min(displayMetrics.widthPixels - width, positionX); + positionX = Math.max(0, positionX); + + if (isShowing()) { + mPopupWindow.update(positionX, positionY, -1, -1); + } else { + mPopupWindow.showAtLocation(mTextView, Gravity.NO_GRAVITY, + positionX, positionY); + } + } + + public void hide() { + mPopupWindow.dismiss(); + getPositionListener().removeSubscriber(this); + } + + @Override + public void updatePosition(int parentPositionX, int parentPositionY, + boolean parentPositionChanged, boolean parentScrolled) { + // Either parentPositionChanged or parentScrolled is true, check if still visible + if (isShowing() && isOffsetVisible(getTextOffset())) { + if (parentScrolled) computeLocalPosition(); + updatePosition(parentPositionX, parentPositionY); + } else { + hide(); + } + } + + public boolean isShowing() { + return mPopupWindow.isShowing(); + } + } + + private class SuggestionsPopupWindow extends PinnedPopupWindow implements OnItemClickListener { + private static final int MAX_NUMBER_SUGGESTIONS = SuggestionSpan.SUGGESTIONS_MAX_SIZE; + private static final int ADD_TO_DICTIONARY = -1; + private static final int DELETE_TEXT = -2; + private SuggestionInfo[] mSuggestionInfos; + private int mNumberOfSuggestions; + private boolean mCursorWasVisibleBeforeSuggestions; + private boolean mIsShowingUp = false; + private SuggestionAdapter mSuggestionsAdapter; + private final Comparator<SuggestionSpan> mSuggestionSpanComparator; + private final HashMap<SuggestionSpan, Integer> mSpansLengths; + + private class CustomPopupWindow extends PopupWindow { + public CustomPopupWindow(Context context, int defStyle) { + super(context, null, defStyle); + } + + @Override + public void dismiss() { + super.dismiss(); + + getPositionListener().removeSubscriber(SuggestionsPopupWindow.this); + + // Safe cast since show() checks that mTextView.getText() is an Editable + ((Spannable) mTextView.getText()).removeSpan(mSuggestionRangeSpan); + + mTextView.setCursorVisible(mCursorWasVisibleBeforeSuggestions); + if (hasInsertionController()) { + getInsertionController().show(); + } + } + } + + public SuggestionsPopupWindow() { + mCursorWasVisibleBeforeSuggestions = mCursorVisible; + mSuggestionSpanComparator = new SuggestionSpanComparator(); + mSpansLengths = new HashMap<SuggestionSpan, Integer>(); + } + + @Override + protected void createPopupWindow() { + mPopupWindow = new CustomPopupWindow(mTextView.getContext(), + com.android.internal.R.attr.textSuggestionsWindowStyle); + mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); + mPopupWindow.setFocusable(true); + mPopupWindow.setClippingEnabled(false); + } + + @Override + protected void initContentView() { + ListView listView = new ListView(mTextView.getContext()); + mSuggestionsAdapter = new SuggestionAdapter(); + listView.setAdapter(mSuggestionsAdapter); + listView.setOnItemClickListener(this); + mContentView = listView; + + // Inflate the suggestion items once and for all. + 2 for add to dictionary and delete + mSuggestionInfos = new SuggestionInfo[MAX_NUMBER_SUGGESTIONS + 2]; + for (int i = 0; i < mSuggestionInfos.length; i++) { + mSuggestionInfos[i] = new SuggestionInfo(); + } + } + + public boolean isShowingUp() { + return mIsShowingUp; + } + + public void onParentLostFocus() { + mIsShowingUp = false; + } + + private class SuggestionInfo { + int suggestionStart, suggestionEnd; // range of actual suggestion within text + SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents + int suggestionIndex; // the index of this suggestion inside suggestionSpan + SpannableStringBuilder text = new SpannableStringBuilder(); + TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mTextView.getContext(), + android.R.style.TextAppearance_SuggestionHighlight); + } + + private class SuggestionAdapter extends BaseAdapter { + private LayoutInflater mInflater = (LayoutInflater) mTextView.getContext(). + getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + @Override + public int getCount() { + return mNumberOfSuggestions; + } + + @Override + public Object getItem(int position) { + return mSuggestionInfos[position]; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + TextView textView = (TextView) convertView; + + if (textView == null) { + textView = (TextView) mInflater.inflate(mTextView.mTextEditSuggestionItemLayout, + parent, false); + } + + final SuggestionInfo suggestionInfo = mSuggestionInfos[position]; + textView.setText(suggestionInfo.text); + + if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY) { + textView.setCompoundDrawablesWithIntrinsicBounds( + com.android.internal.R.drawable.ic_suggestions_add, 0, 0, 0); + } else if (suggestionInfo.suggestionIndex == DELETE_TEXT) { + textView.setCompoundDrawablesWithIntrinsicBounds( + com.android.internal.R.drawable.ic_suggestions_delete, 0, 0, 0); + } else { + textView.setCompoundDrawables(null, null, null, null); + } + + return textView; + } + } + + private class SuggestionSpanComparator implements Comparator<SuggestionSpan> { + public int compare(SuggestionSpan span1, SuggestionSpan span2) { + final int flag1 = span1.getFlags(); + final int flag2 = span2.getFlags(); + if (flag1 != flag2) { + // The order here should match what is used in updateDrawState + final boolean easy1 = (flag1 & SuggestionSpan.FLAG_EASY_CORRECT) != 0; + final boolean easy2 = (flag2 & SuggestionSpan.FLAG_EASY_CORRECT) != 0; + final boolean misspelled1 = (flag1 & SuggestionSpan.FLAG_MISSPELLED) != 0; + final boolean misspelled2 = (flag2 & SuggestionSpan.FLAG_MISSPELLED) != 0; + if (easy1 && !misspelled1) return -1; + if (easy2 && !misspelled2) return 1; + if (misspelled1) return -1; + if (misspelled2) return 1; + } + + return mSpansLengths.get(span1).intValue() - mSpansLengths.get(span2).intValue(); + } + } + + /** + * Returns the suggestion spans that cover the current cursor position. The suggestion + * spans are sorted according to the length of text that they are attached to. + */ + private SuggestionSpan[] getSuggestionSpans() { + int pos = mTextView.getSelectionStart(); + Spannable spannable = (Spannable) mTextView.getText(); + SuggestionSpan[] suggestionSpans = spannable.getSpans(pos, pos, SuggestionSpan.class); + + mSpansLengths.clear(); + for (SuggestionSpan suggestionSpan : suggestionSpans) { + int start = spannable.getSpanStart(suggestionSpan); + int end = spannable.getSpanEnd(suggestionSpan); + mSpansLengths.put(suggestionSpan, Integer.valueOf(end - start)); + } + + // The suggestions are sorted according to their types (easy correction first, then + // misspelled) and to the length of the text that they cover (shorter first). + Arrays.sort(suggestionSpans, mSuggestionSpanComparator); + return suggestionSpans; + } + + @Override + public void show() { + if (!(mTextView.getText() instanceof Editable)) return; + + if (updateSuggestions()) { + mCursorWasVisibleBeforeSuggestions = mCursorVisible; + mTextView.setCursorVisible(false); + mIsShowingUp = true; + super.show(); + } + } + + @Override + protected void measureContent() { + final DisplayMetrics displayMetrics = mTextView.getResources().getDisplayMetrics(); + final int horizontalMeasure = View.MeasureSpec.makeMeasureSpec( + displayMetrics.widthPixels, View.MeasureSpec.AT_MOST); + final int verticalMeasure = View.MeasureSpec.makeMeasureSpec( + displayMetrics.heightPixels, View.MeasureSpec.AT_MOST); + + int width = 0; + View view = null; + for (int i = 0; i < mNumberOfSuggestions; i++) { + view = mSuggestionsAdapter.getView(i, view, mContentView); + view.getLayoutParams().width = LayoutParams.WRAP_CONTENT; + view.measure(horizontalMeasure, verticalMeasure); + width = Math.max(width, view.getMeasuredWidth()); + } + + // Enforce the width based on actual text widths + mContentView.measure( + View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), + verticalMeasure); + + Drawable popupBackground = mPopupWindow.getBackground(); + if (popupBackground != null) { + if (mTempRect == null) mTempRect = new Rect(); + popupBackground.getPadding(mTempRect); + width += mTempRect.left + mTempRect.right; + } + mPopupWindow.setWidth(width); + } + + @Override + protected int getTextOffset() { + return mTextView.getSelectionStart(); + } + + @Override + protected int getVerticalLocalPosition(int line) { + return mTextView.getLayout().getLineBottom(line); + } + + @Override + protected int clipVertically(int positionY) { + final int height = mContentView.getMeasuredHeight(); + final DisplayMetrics displayMetrics = mTextView.getResources().getDisplayMetrics(); + return Math.min(positionY, displayMetrics.heightPixels - height); + } + + @Override + public void hide() { + super.hide(); + } + + private boolean updateSuggestions() { + Spannable spannable = (Spannable) mTextView.getText(); + SuggestionSpan[] suggestionSpans = getSuggestionSpans(); + + final int nbSpans = suggestionSpans.length; + // Suggestions are shown after a delay: the underlying spans may have been removed + if (nbSpans == 0) return false; + + mNumberOfSuggestions = 0; + int spanUnionStart = mTextView.getText().length(); + int spanUnionEnd = 0; + + SuggestionSpan misspelledSpan = null; + int underlineColor = 0; + + for (int spanIndex = 0; spanIndex < nbSpans; spanIndex++) { + SuggestionSpan suggestionSpan = suggestionSpans[spanIndex]; + final int spanStart = spannable.getSpanStart(suggestionSpan); + final int spanEnd = spannable.getSpanEnd(suggestionSpan); + spanUnionStart = Math.min(spanStart, spanUnionStart); + spanUnionEnd = Math.max(spanEnd, spanUnionEnd); + + if ((suggestionSpan.getFlags() & SuggestionSpan.FLAG_MISSPELLED) != 0) { + misspelledSpan = suggestionSpan; + } + + // The first span dictates the background color of the highlighted text + if (spanIndex == 0) underlineColor = suggestionSpan.getUnderlineColor(); + + String[] suggestions = suggestionSpan.getSuggestions(); + int nbSuggestions = suggestions.length; + for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) { + String suggestion = suggestions[suggestionIndex]; + + boolean suggestionIsDuplicate = false; + for (int i = 0; i < mNumberOfSuggestions; i++) { + if (mSuggestionInfos[i].text.toString().equals(suggestion)) { + SuggestionSpan otherSuggestionSpan = mSuggestionInfos[i].suggestionSpan; + final int otherSpanStart = spannable.getSpanStart(otherSuggestionSpan); + final int otherSpanEnd = spannable.getSpanEnd(otherSuggestionSpan); + if (spanStart == otherSpanStart && spanEnd == otherSpanEnd) { + suggestionIsDuplicate = true; + break; + } + } + } + + if (!suggestionIsDuplicate) { + SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions]; + suggestionInfo.suggestionSpan = suggestionSpan; + suggestionInfo.suggestionIndex = suggestionIndex; + suggestionInfo.text.replace(0, suggestionInfo.text.length(), suggestion); + + mNumberOfSuggestions++; + + if (mNumberOfSuggestions == MAX_NUMBER_SUGGESTIONS) { + // Also end outer for loop + spanIndex = nbSpans; + break; + } + } + } + } + + for (int i = 0; i < mNumberOfSuggestions; i++) { + highlightTextDifferences(mSuggestionInfos[i], spanUnionStart, spanUnionEnd); + } + + // Add "Add to dictionary" item if there is a span with the misspelled flag + if (misspelledSpan != null) { + final int misspelledStart = spannable.getSpanStart(misspelledSpan); + final int misspelledEnd = spannable.getSpanEnd(misspelledSpan); + if (misspelledStart >= 0 && misspelledEnd > misspelledStart) { + SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions]; + suggestionInfo.suggestionSpan = misspelledSpan; + suggestionInfo.suggestionIndex = ADD_TO_DICTIONARY; + suggestionInfo.text.replace(0, suggestionInfo.text.length(), mTextView. + getContext().getString(com.android.internal.R.string.addToDictionary)); + suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, 0, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + mNumberOfSuggestions++; + } + } + + // Delete item + SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions]; + suggestionInfo.suggestionSpan = null; + suggestionInfo.suggestionIndex = DELETE_TEXT; + suggestionInfo.text.replace(0, suggestionInfo.text.length(), + mTextView.getContext().getString(com.android.internal.R.string.deleteText)); + suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, 0, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + mNumberOfSuggestions++; + + if (mSuggestionRangeSpan == null) mSuggestionRangeSpan = new SuggestionRangeSpan(); + if (underlineColor == 0) { + // Fallback on the default highlight color when the first span does not provide one + mSuggestionRangeSpan.setBackgroundColor(mTextView.mHighlightColor); + } else { + final float BACKGROUND_TRANSPARENCY = 0.4f; + final int newAlpha = (int) (Color.alpha(underlineColor) * BACKGROUND_TRANSPARENCY); + mSuggestionRangeSpan.setBackgroundColor( + (underlineColor & 0x00FFFFFF) + (newAlpha << 24)); + } + spannable.setSpan(mSuggestionRangeSpan, spanUnionStart, spanUnionEnd, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + mSuggestionsAdapter.notifyDataSetChanged(); + return true; + } + + private void highlightTextDifferences(SuggestionInfo suggestionInfo, int unionStart, + int unionEnd) { + final Spannable text = (Spannable) mTextView.getText(); + final int spanStart = text.getSpanStart(suggestionInfo.suggestionSpan); + final int spanEnd = text.getSpanEnd(suggestionInfo.suggestionSpan); + + // Adjust the start/end of the suggestion span + suggestionInfo.suggestionStart = spanStart - unionStart; + suggestionInfo.suggestionEnd = suggestionInfo.suggestionStart + + suggestionInfo.text.length(); + + suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, + suggestionInfo.text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + + // Add the text before and after the span. + final String textAsString = text.toString(); + suggestionInfo.text.insert(0, textAsString.substring(unionStart, spanStart)); + suggestionInfo.text.append(textAsString.substring(spanEnd, unionEnd)); + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + Editable editable = (Editable) mTextView.getText(); + SuggestionInfo suggestionInfo = mSuggestionInfos[position]; + + if (suggestionInfo.suggestionIndex == DELETE_TEXT) { + final int spanUnionStart = editable.getSpanStart(mSuggestionRangeSpan); + int spanUnionEnd = editable.getSpanEnd(mSuggestionRangeSpan); + if (spanUnionStart >= 0 && spanUnionEnd > spanUnionStart) { + // Do not leave two adjacent spaces after deletion, or one at beginning of text + if (spanUnionEnd < editable.length() && + Character.isSpaceChar(editable.charAt(spanUnionEnd)) && + (spanUnionStart == 0 || + Character.isSpaceChar(editable.charAt(spanUnionStart - 1)))) { + spanUnionEnd = spanUnionEnd + 1; + } + mTextView.deleteText_internal(spanUnionStart, spanUnionEnd); + } + hide(); + return; + } + + final int spanStart = editable.getSpanStart(suggestionInfo.suggestionSpan); + final int spanEnd = editable.getSpanEnd(suggestionInfo.suggestionSpan); + if (spanStart < 0 || spanEnd <= spanStart) { + // Span has been removed + hide(); + return; + } + + final String originalText = editable.toString().substring(spanStart, spanEnd); + + if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY) { + Intent intent = new Intent(Settings.ACTION_USER_DICTIONARY_INSERT); + intent.putExtra("word", originalText); + intent.putExtra("locale", mTextView.getTextServicesLocale().toString()); + intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); + mTextView.getContext().startActivity(intent); + // There is no way to know if the word was indeed added. Re-check. + // TODO The ExtractEditText should remove the span in the original text instead + editable.removeSpan(suggestionInfo.suggestionSpan); + updateSpellCheckSpans(spanStart, spanEnd, false); + } else { + // SuggestionSpans are removed by replace: save them before + SuggestionSpan[] suggestionSpans = editable.getSpans(spanStart, spanEnd, + SuggestionSpan.class); + final int length = suggestionSpans.length; + int[] suggestionSpansStarts = new int[length]; + int[] suggestionSpansEnds = new int[length]; + int[] suggestionSpansFlags = new int[length]; + for (int i = 0; i < length; i++) { + final SuggestionSpan suggestionSpan = suggestionSpans[i]; + suggestionSpansStarts[i] = editable.getSpanStart(suggestionSpan); + suggestionSpansEnds[i] = editable.getSpanEnd(suggestionSpan); + suggestionSpansFlags[i] = editable.getSpanFlags(suggestionSpan); + + // Remove potential misspelled flags + int suggestionSpanFlags = suggestionSpan.getFlags(); + if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) > 0) { + suggestionSpanFlags &= ~SuggestionSpan.FLAG_MISSPELLED; + suggestionSpanFlags &= ~SuggestionSpan.FLAG_EASY_CORRECT; + suggestionSpan.setFlags(suggestionSpanFlags); + } + } + + final int suggestionStart = suggestionInfo.suggestionStart; + final int suggestionEnd = suggestionInfo.suggestionEnd; + final String suggestion = suggestionInfo.text.subSequence( + suggestionStart, suggestionEnd).toString(); + mTextView.replaceText_internal(spanStart, spanEnd, suggestion); + + // Notify source IME of the suggestion pick. Do this before swaping texts. + if (!TextUtils.isEmpty( + suggestionInfo.suggestionSpan.getNotificationTargetClassName())) { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + imm.notifySuggestionPicked(suggestionInfo.suggestionSpan, originalText, + suggestionInfo.suggestionIndex); + } + } + + // Swap text content between actual text and Suggestion span + String[] suggestions = suggestionInfo.suggestionSpan.getSuggestions(); + suggestions[suggestionInfo.suggestionIndex] = originalText; + + // Restore previous SuggestionSpans + final int lengthDifference = suggestion.length() - (spanEnd - spanStart); + for (int i = 0; i < length; i++) { + // Only spans that include the modified region make sense after replacement + // Spans partially included in the replaced region are removed, there is no + // way to assign them a valid range after replacement + if (suggestionSpansStarts[i] <= spanStart && + suggestionSpansEnds[i] >= spanEnd) { + mTextView.setSpan_internal(suggestionSpans[i], suggestionSpansStarts[i], + suggestionSpansEnds[i] + lengthDifference, suggestionSpansFlags[i]); + } + } + + // Move cursor at the end of the replaced word + final int newCursorPosition = spanEnd + lengthDifference; + mTextView.setCursorPosition_internal(newCursorPosition, newCursorPosition); + } + + hide(); + } + } + + /** + * An ActionMode Callback class that is used to provide actions while in text selection mode. + * + * The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending + * on which of these this TextView supports. + */ + private class SelectionActionModeCallback implements ActionMode.Callback { + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + TypedArray styledAttributes = mTextView.getContext().obtainStyledAttributes( + com.android.internal.R.styleable.SelectionModeDrawables); + + boolean allowText = mTextView.getContext().getResources().getBoolean( + com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon); + + mode.setTitle(mTextView.getContext().getString( + com.android.internal.R.string.textSelectionCABTitle)); + mode.setSubtitle(null); + mode.setTitleOptionalHint(true); + + int selectAllIconId = 0; // No icon by default + if (!allowText) { + // Provide an icon, text will not be displayed on smaller screens. + selectAllIconId = styledAttributes.getResourceId( + R.styleable.SelectionModeDrawables_actionModeSelectAllDrawable, 0); + } + + menu.add(0, TextView.ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll). + setIcon(selectAllIconId). + setAlphabeticShortcut('a'). + setShowAsAction( + MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + + if (mTextView.canCut()) { + menu.add(0, TextView.ID_CUT, 0, com.android.internal.R.string.cut). + setIcon(styledAttributes.getResourceId( + R.styleable.SelectionModeDrawables_actionModeCutDrawable, 0)). + setAlphabeticShortcut('x'). + setShowAsAction( + MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + + if (mTextView.canCopy()) { + menu.add(0, TextView.ID_COPY, 0, com.android.internal.R.string.copy). + setIcon(styledAttributes.getResourceId( + R.styleable.SelectionModeDrawables_actionModeCopyDrawable, 0)). + setAlphabeticShortcut('c'). + setShowAsAction( + MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + + if (mTextView.canPaste()) { + menu.add(0, TextView.ID_PASTE, 0, com.android.internal.R.string.paste). + setIcon(styledAttributes.getResourceId( + R.styleable.SelectionModeDrawables_actionModePasteDrawable, 0)). + setAlphabeticShortcut('v'). + setShowAsAction( + MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); + } + + styledAttributes.recycle(); + + if (mCustomSelectionActionModeCallback != null) { + if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) { + // The custom mode can choose to cancel the action mode + return false; + } + } + + if (menu.hasVisibleItems() || mode.getCustomView() != null) { + getSelectionController().show(); + return true; + } else { + return false; + } + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + if (mCustomSelectionActionModeCallback != null) { + return mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu); + } + return true; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + if (mCustomSelectionActionModeCallback != null && + mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) { + return true; + } + return mTextView.onTextContextMenuItem(item.getItemId()); + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + if (mCustomSelectionActionModeCallback != null) { + mCustomSelectionActionModeCallback.onDestroyActionMode(mode); + } + Selection.setSelection((Spannable) mTextView.getText(), mTextView.getSelectionEnd()); + + if (mSelectionModifierCursorController != null) { + mSelectionModifierCursorController.hide(); + } + + mSelectionActionMode = null; + } + } + + private class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener { + private static final int POPUP_TEXT_LAYOUT = + com.android.internal.R.layout.text_edit_action_popup_text; + private TextView mPasteTextView; + private TextView mReplaceTextView; + + @Override + protected void createPopupWindow() { + mPopupWindow = new PopupWindow(mTextView.getContext(), null, + com.android.internal.R.attr.textSelectHandleWindowStyle); + mPopupWindow.setClippingEnabled(true); + } + + @Override + protected void initContentView() { + LinearLayout linearLayout = new LinearLayout(mTextView.getContext()); + linearLayout.setOrientation(LinearLayout.HORIZONTAL); + mContentView = linearLayout; + mContentView.setBackgroundResource( + com.android.internal.R.drawable.text_edit_paste_window); + + LayoutInflater inflater = (LayoutInflater) mTextView.getContext(). + getSystemService(Context.LAYOUT_INFLATER_SERVICE); + + LayoutParams wrapContent = new LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + + mPasteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null); + mPasteTextView.setLayoutParams(wrapContent); + mContentView.addView(mPasteTextView); + mPasteTextView.setText(com.android.internal.R.string.paste); + mPasteTextView.setOnClickListener(this); + + mReplaceTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null); + mReplaceTextView.setLayoutParams(wrapContent); + mContentView.addView(mReplaceTextView); + mReplaceTextView.setText(com.android.internal.R.string.replace); + mReplaceTextView.setOnClickListener(this); + } + + @Override + public void show() { + boolean canPaste = mTextView.canPaste(); + boolean canSuggest = mTextView.isSuggestionsEnabled() && isCursorInsideSuggestionSpan(); + mPasteTextView.setVisibility(canPaste ? View.VISIBLE : View.GONE); + mReplaceTextView.setVisibility(canSuggest ? View.VISIBLE : View.GONE); + + if (!canPaste && !canSuggest) return; + + super.show(); + } + + @Override + public void onClick(View view) { + if (view == mPasteTextView && mTextView.canPaste()) { + mTextView.onTextContextMenuItem(TextView.ID_PASTE); + hide(); + } else if (view == mReplaceTextView) { + int middle = (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2; + stopSelectionActionMode(); + Selection.setSelection((Spannable) mTextView.getText(), middle); + showSuggestions(); + } + } + + @Override + protected int getTextOffset() { + return (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2; + } + + @Override + protected int getVerticalLocalPosition(int line) { + return mTextView.getLayout().getLineTop(line) - mContentView.getMeasuredHeight(); + } + + @Override + protected int clipVertically(int positionY) { + if (positionY < 0) { + final int offset = getTextOffset(); + final Layout layout = mTextView.getLayout(); + final int line = layout.getLineForOffset(offset); + positionY += layout.getLineBottom(line) - layout.getLineTop(line); + positionY += mContentView.getMeasuredHeight(); + + // Assumes insertion and selection handles share the same height + final Drawable handle = mTextView.getResources().getDrawable( + mTextView.mTextSelectHandleRes); + positionY += handle.getIntrinsicHeight(); + } + + return positionY; + } + } + + private abstract class HandleView extends View implements TextViewPositionListener { + protected Drawable mDrawable; + protected Drawable mDrawableLtr; + protected Drawable mDrawableRtl; + private final PopupWindow mContainer; + // Position with respect to the parent TextView + private int mPositionX, mPositionY; + private boolean mIsDragging; + // Offset from touch position to mPosition + private float mTouchToWindowOffsetX, mTouchToWindowOffsetY; + protected int mHotspotX; + // Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up + private float mTouchOffsetY; + // Where the touch position should be on the handle to ensure a maximum cursor visibility + private float mIdealVerticalOffset; + // Parent's (TextView) previous position in window + private int mLastParentX, mLastParentY; + // Transient action popup window for Paste and Replace actions + protected ActionPopupWindow mActionPopupWindow; + // Previous text character offset + private int mPreviousOffset = -1; + // Previous text character offset + private boolean mPositionHasChanged = true; + // Used to delay the appearance of the action popup window + private Runnable mActionPopupShower; + + public HandleView(Drawable drawableLtr, Drawable drawableRtl) { + super(mTextView.getContext()); + mContainer = new PopupWindow(mTextView.getContext(), null, + com.android.internal.R.attr.textSelectHandleWindowStyle); + mContainer.setSplitTouchEnabled(true); + mContainer.setClippingEnabled(false); + mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL); + mContainer.setContentView(this); + + mDrawableLtr = drawableLtr; + mDrawableRtl = drawableRtl; + + updateDrawable(); + + final int handleHeight = mDrawable.getIntrinsicHeight(); + mTouchOffsetY = -0.3f * handleHeight; + mIdealVerticalOffset = 0.7f * handleHeight; + } + + protected void updateDrawable() { + final int offset = getCurrentCursorOffset(); + final boolean isRtlCharAtOffset = mTextView.getLayout().isRtlCharAt(offset); + mDrawable = isRtlCharAtOffset ? mDrawableRtl : mDrawableLtr; + mHotspotX = getHotspotX(mDrawable, isRtlCharAtOffset); + } + + protected abstract int getHotspotX(Drawable drawable, boolean isRtlRun); + + // Touch-up filter: number of previous positions remembered + private static final int HISTORY_SIZE = 5; + private static final int TOUCH_UP_FILTER_DELAY_AFTER = 150; + private static final int TOUCH_UP_FILTER_DELAY_BEFORE = 350; + private final long[] mPreviousOffsetsTimes = new long[HISTORY_SIZE]; + private final int[] mPreviousOffsets = new int[HISTORY_SIZE]; + private int mPreviousOffsetIndex = 0; + private int mNumberPreviousOffsets = 0; + + private void startTouchUpFilter(int offset) { + mNumberPreviousOffsets = 0; + addPositionToTouchUpFilter(offset); + } + + private void addPositionToTouchUpFilter(int offset) { + mPreviousOffsetIndex = (mPreviousOffsetIndex + 1) % HISTORY_SIZE; + mPreviousOffsets[mPreviousOffsetIndex] = offset; + mPreviousOffsetsTimes[mPreviousOffsetIndex] = SystemClock.uptimeMillis(); + mNumberPreviousOffsets++; + } + + private void filterOnTouchUp() { + final long now = SystemClock.uptimeMillis(); + int i = 0; + int index = mPreviousOffsetIndex; + final int iMax = Math.min(mNumberPreviousOffsets, HISTORY_SIZE); + while (i < iMax && (now - mPreviousOffsetsTimes[index]) < TOUCH_UP_FILTER_DELAY_AFTER) { + i++; + index = (mPreviousOffsetIndex - i + HISTORY_SIZE) % HISTORY_SIZE; + } + + if (i > 0 && i < iMax && + (now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) { + positionAtCursorOffset(mPreviousOffsets[index], false); + } + } + + public boolean offsetHasBeenChanged() { + return mNumberPreviousOffsets > 1; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()); + } + + public void show() { + if (isShowing()) return; + + getPositionListener().addSubscriber(this, true /* local position may change */); + + // Make sure the offset is always considered new, even when focusing at same position + mPreviousOffset = -1; + positionAtCursorOffset(getCurrentCursorOffset(), false); + + hideActionPopupWindow(); + } + + protected void dismiss() { + mIsDragging = false; + mContainer.dismiss(); + onDetached(); + } + + public void hide() { + dismiss(); + + getPositionListener().removeSubscriber(this); + } + + void showActionPopupWindow(int delay) { + if (mActionPopupWindow == null) { + mActionPopupWindow = new ActionPopupWindow(); + } + if (mActionPopupShower == null) { + mActionPopupShower = new Runnable() { + public void run() { + mActionPopupWindow.show(); + } + }; + } else { + mTextView.removeCallbacks(mActionPopupShower); + } + mTextView.postDelayed(mActionPopupShower, delay); + } + + protected void hideActionPopupWindow() { + if (mActionPopupShower != null) { + mTextView.removeCallbacks(mActionPopupShower); + } + if (mActionPopupWindow != null) { + mActionPopupWindow.hide(); + } + } + + public boolean isShowing() { + return mContainer.isShowing(); + } + + private boolean isVisible() { + // Always show a dragging handle. + if (mIsDragging) { + return true; + } + + if (mTextView.isInBatchEditMode()) { + return false; + } + + return isPositionVisible(mPositionX + mHotspotX, mPositionY); + } + + public abstract int getCurrentCursorOffset(); + + protected abstract void updateSelection(int offset); + + public abstract void updatePosition(float x, float y); + + protected void positionAtCursorOffset(int offset, boolean parentScrolled) { + // A HandleView relies on the layout, which may be nulled by external methods + Layout layout = mTextView.getLayout(); + if (layout == null) { + // Will update controllers' state, hiding them and stopping selection mode if needed + prepareCursorControllers(); + return; + } + + boolean offsetChanged = offset != mPreviousOffset; + if (offsetChanged || parentScrolled) { + if (offsetChanged) { + updateSelection(offset); + addPositionToTouchUpFilter(offset); + } + final int line = layout.getLineForOffset(offset); + + mPositionX = (int) (layout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX); + mPositionY = layout.getLineBottom(line); + + // Take TextView's padding and scroll into account. + mPositionX += mTextView.viewportToContentHorizontalOffset(); + mPositionY += mTextView.viewportToContentVerticalOffset(); + + mPreviousOffset = offset; + mPositionHasChanged = true; + } + } + + public void updatePosition(int parentPositionX, int parentPositionY, + boolean parentPositionChanged, boolean parentScrolled) { + positionAtCursorOffset(getCurrentCursorOffset(), parentScrolled); + if (parentPositionChanged || mPositionHasChanged) { + if (mIsDragging) { + // Update touchToWindow offset in case of parent scrolling while dragging + if (parentPositionX != mLastParentX || parentPositionY != mLastParentY) { + mTouchToWindowOffsetX += parentPositionX - mLastParentX; + mTouchToWindowOffsetY += parentPositionY - mLastParentY; + mLastParentX = parentPositionX; + mLastParentY = parentPositionY; + } + + onHandleMoved(); + } + + if (isVisible()) { + final int positionX = parentPositionX + mPositionX; + final int positionY = parentPositionY + mPositionY; + if (isShowing()) { + mContainer.update(positionX, positionY, -1, -1); + } else { + mContainer.showAtLocation(mTextView, Gravity.NO_GRAVITY, + positionX, positionY); + } + } else { + if (isShowing()) { + dismiss(); + } + } + + mPositionHasChanged = false; + } + } + + @Override + protected void onDraw(Canvas c) { + mDrawable.setBounds(0, 0, mRight - mLeft, mBottom - mTop); + mDrawable.draw(c); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + switch (ev.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + startTouchUpFilter(getCurrentCursorOffset()); + mTouchToWindowOffsetX = ev.getRawX() - mPositionX; + mTouchToWindowOffsetY = ev.getRawY() - mPositionY; + + final PositionListener positionListener = getPositionListener(); + mLastParentX = positionListener.getPositionX(); + mLastParentY = positionListener.getPositionY(); + mIsDragging = true; + break; + } + + case MotionEvent.ACTION_MOVE: { + final float rawX = ev.getRawX(); + final float rawY = ev.getRawY(); + + // Vertical hysteresis: vertical down movement tends to snap to ideal offset + final float previousVerticalOffset = mTouchToWindowOffsetY - mLastParentY; + final float currentVerticalOffset = rawY - mPositionY - mLastParentY; + float newVerticalOffset; + if (previousVerticalOffset < mIdealVerticalOffset) { + newVerticalOffset = Math.min(currentVerticalOffset, mIdealVerticalOffset); + newVerticalOffset = Math.max(newVerticalOffset, previousVerticalOffset); + } else { + newVerticalOffset = Math.max(currentVerticalOffset, mIdealVerticalOffset); + newVerticalOffset = Math.min(newVerticalOffset, previousVerticalOffset); + } + mTouchToWindowOffsetY = newVerticalOffset + mLastParentY; + + final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX; + final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY; + + updatePosition(newPosX, newPosY); + break; + } + + case MotionEvent.ACTION_UP: + filterOnTouchUp(); + mIsDragging = false; + break; + + case MotionEvent.ACTION_CANCEL: + mIsDragging = false; + break; + } + return true; + } + + public boolean isDragging() { + return mIsDragging; + } + + void onHandleMoved() { + hideActionPopupWindow(); + } + + public void onDetached() { + hideActionPopupWindow(); + } + } + + private class InsertionHandleView extends HandleView { + private static final int DELAY_BEFORE_HANDLE_FADES_OUT = 4000; + private static final int RECENT_CUT_COPY_DURATION = 15 * 1000; // seconds + + // Used to detect taps on the insertion handle, which will affect the ActionPopupWindow + private float mDownPositionX, mDownPositionY; + private Runnable mHider; + + public InsertionHandleView(Drawable drawable) { + super(drawable, drawable); + } + + @Override + public void show() { + super.show(); + + final long durationSinceCutOrCopy = + SystemClock.uptimeMillis() - TextView.LAST_CUT_OR_COPY_TIME; + if (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION) { + showActionPopupWindow(0); + } + + hideAfterDelay(); + } + + public void showWithActionPopup() { + show(); + showActionPopupWindow(0); + } + + private void hideAfterDelay() { + if (mHider == null) { + mHider = new Runnable() { + public void run() { + hide(); + } + }; + } else { + removeHiderCallback(); + } + mTextView.postDelayed(mHider, DELAY_BEFORE_HANDLE_FADES_OUT); + } + + private void removeHiderCallback() { + if (mHider != null) { + mTextView.removeCallbacks(mHider); + } + } + + @Override + protected int getHotspotX(Drawable drawable, boolean isRtlRun) { + return drawable.getIntrinsicWidth() / 2; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + final boolean result = super.onTouchEvent(ev); + + switch (ev.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mDownPositionX = ev.getRawX(); + mDownPositionY = ev.getRawY(); + break; + + case MotionEvent.ACTION_UP: + if (!offsetHasBeenChanged()) { + final float deltaX = mDownPositionX - ev.getRawX(); + final float deltaY = mDownPositionY - ev.getRawY(); + final float distanceSquared = deltaX * deltaX + deltaY * deltaY; + + final ViewConfiguration viewConfiguration = ViewConfiguration.get( + mTextView.getContext()); + final int touchSlop = viewConfiguration.getScaledTouchSlop(); + + if (distanceSquared < touchSlop * touchSlop) { + if (mActionPopupWindow != null && mActionPopupWindow.isShowing()) { + // Tapping on the handle dismisses the displayed action popup + mActionPopupWindow.hide(); + } else { + showWithActionPopup(); + } + } + } + hideAfterDelay(); + break; + + case MotionEvent.ACTION_CANCEL: + hideAfterDelay(); + break; + + default: + break; + } + + return result; + } + + @Override + public int getCurrentCursorOffset() { + return mTextView.getSelectionStart(); + } + + @Override + public void updateSelection(int offset) { + Selection.setSelection((Spannable) mTextView.getText(), offset); + } + + @Override + public void updatePosition(float x, float y) { + positionAtCursorOffset(mTextView.getOffsetForPosition(x, y), false); + } + + @Override + void onHandleMoved() { + super.onHandleMoved(); + removeHiderCallback(); + } + + @Override + public void onDetached() { + super.onDetached(); + removeHiderCallback(); + } + } + + private class SelectionStartHandleView extends HandleView { + + public SelectionStartHandleView(Drawable drawableLtr, Drawable drawableRtl) { + super(drawableLtr, drawableRtl); + } + + @Override + protected int getHotspotX(Drawable drawable, boolean isRtlRun) { + if (isRtlRun) { + return drawable.getIntrinsicWidth() / 4; + } else { + return (drawable.getIntrinsicWidth() * 3) / 4; + } + } + + @Override + public int getCurrentCursorOffset() { + return mTextView.getSelectionStart(); + } + + @Override + public void updateSelection(int offset) { + Selection.setSelection((Spannable) mTextView.getText(), offset, + mTextView.getSelectionEnd()); + updateDrawable(); + } + + @Override + public void updatePosition(float x, float y) { + int offset = mTextView.getOffsetForPosition(x, y); + + // Handles can not cross and selection is at least one character + final int selectionEnd = mTextView.getSelectionEnd(); + if (offset >= selectionEnd) offset = Math.max(0, selectionEnd - 1); + + positionAtCursorOffset(offset, false); + } + + public ActionPopupWindow getActionPopupWindow() { + return mActionPopupWindow; + } + } + + private class SelectionEndHandleView extends HandleView { + + public SelectionEndHandleView(Drawable drawableLtr, Drawable drawableRtl) { + super(drawableLtr, drawableRtl); + } + + @Override + protected int getHotspotX(Drawable drawable, boolean isRtlRun) { + if (isRtlRun) { + return (drawable.getIntrinsicWidth() * 3) / 4; + } else { + return drawable.getIntrinsicWidth() / 4; + } + } + + @Override + public int getCurrentCursorOffset() { + return mTextView.getSelectionEnd(); + } + + @Override + public void updateSelection(int offset) { + Selection.setSelection((Spannable) mTextView.getText(), + mTextView.getSelectionStart(), offset); + updateDrawable(); + } + + @Override + public void updatePosition(float x, float y) { + int offset = mTextView.getOffsetForPosition(x, y); + + // Handles can not cross and selection is at least one character + final int selectionStart = mTextView.getSelectionStart(); + if (offset <= selectionStart) { + offset = Math.min(selectionStart + 1, mTextView.getText().length()); + } + + positionAtCursorOffset(offset, false); + } + + public void setActionPopupWindow(ActionPopupWindow actionPopupWindow) { + mActionPopupWindow = actionPopupWindow; + } + } + + /** + * A CursorController instance can be used to control a cursor in the text. + */ + private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener { + /** + * Makes the cursor controller visible on screen. + * See also {@link #hide()}. + */ + public void show(); + + /** + * Hide the cursor controller from screen. + * See also {@link #show()}. + */ + public void hide(); + + /** + * Called when the view is detached from window. Perform house keeping task, such as + * stopping Runnable thread that would otherwise keep a reference on the context, thus + * preventing the activity from being recycled. + */ + public void onDetached(); + } + + private class InsertionPointCursorController implements CursorController { + private InsertionHandleView mHandle; + + public void show() { + getHandle().show(); + } + + public void showWithActionPopup() { + getHandle().showWithActionPopup(); + } + + public void hide() { + if (mHandle != null) { + mHandle.hide(); + } + } + + public void onTouchModeChanged(boolean isInTouchMode) { + if (!isInTouchMode) { + hide(); + } + } + + private InsertionHandleView getHandle() { + if (mSelectHandleCenter == null) { + mSelectHandleCenter = mTextView.getResources().getDrawable( + mTextView.mTextSelectHandleRes); + } + if (mHandle == null) { + mHandle = new InsertionHandleView(mSelectHandleCenter); + } + return mHandle; + } + + @Override + public void onDetached() { + final ViewTreeObserver observer = mTextView.getViewTreeObserver(); + observer.removeOnTouchModeChangeListener(this); + + if (mHandle != null) mHandle.onDetached(); + } + } + + class SelectionModifierCursorController implements CursorController { + private static final int DELAY_BEFORE_REPLACE_ACTION = 200; // milliseconds + // The cursor controller handles, lazily created when shown. + private SelectionStartHandleView mStartHandle; + private SelectionEndHandleView mEndHandle; + // The offsets of that last touch down event. Remembered to start selection there. + private int mMinTouchOffset, mMaxTouchOffset; + + // Double tap detection + private long mPreviousTapUpTime = 0; + private float mDownPositionX, mDownPositionY; + private boolean mGestureStayedInTapRegion; + + SelectionModifierCursorController() { + resetTouchOffsets(); + } + + public void show() { + if (mTextView.isInBatchEditMode()) { + return; + } + initDrawables(); + initHandles(); + hideInsertionPointCursorController(); + } + + private void initDrawables() { + if (mSelectHandleLeft == null) { + mSelectHandleLeft = mTextView.getContext().getResources().getDrawable( + mTextView.mTextSelectHandleLeftRes); + } + if (mSelectHandleRight == null) { + mSelectHandleRight = mTextView.getContext().getResources().getDrawable( + mTextView.mTextSelectHandleRightRes); + } + } + + private void initHandles() { + // Lazy object creation has to be done before updatePosition() is called. + if (mStartHandle == null) { + mStartHandle = new SelectionStartHandleView(mSelectHandleLeft, mSelectHandleRight); + } + if (mEndHandle == null) { + mEndHandle = new SelectionEndHandleView(mSelectHandleRight, mSelectHandleLeft); + } + + mStartHandle.show(); + mEndHandle.show(); + + // Make sure both left and right handles share the same ActionPopupWindow (so that + // moving any of the handles hides the action popup). + mStartHandle.showActionPopupWindow(DELAY_BEFORE_REPLACE_ACTION); + mEndHandle.setActionPopupWindow(mStartHandle.getActionPopupWindow()); + + hideInsertionPointCursorController(); + } + + public void hide() { + if (mStartHandle != null) mStartHandle.hide(); + if (mEndHandle != null) mEndHandle.hide(); + } + + public void onTouchEvent(MotionEvent event) { + // This is done even when the View does not have focus, so that long presses can start + // selection and tap can move cursor from this tap position. + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + final float x = event.getX(); + final float y = event.getY(); + + // Remember finger down position, to be able to start selection from there + mMinTouchOffset = mMaxTouchOffset = mTextView.getOffsetForPosition(x, y); + + // Double tap detection + if (mGestureStayedInTapRegion) { + long duration = SystemClock.uptimeMillis() - mPreviousTapUpTime; + if (duration <= ViewConfiguration.getDoubleTapTimeout()) { + final float deltaX = x - mDownPositionX; + final float deltaY = y - mDownPositionY; + final float distanceSquared = deltaX * deltaX + deltaY * deltaY; + + ViewConfiguration viewConfiguration = ViewConfiguration.get( + mTextView.getContext()); + int doubleTapSlop = viewConfiguration.getScaledDoubleTapSlop(); + boolean stayedInArea = distanceSquared < doubleTapSlop * doubleTapSlop; + + if (stayedInArea && isPositionOnText(x, y)) { + startSelectionActionMode(); + mDiscardNextActionUp = true; + } + } + } + + mDownPositionX = x; + mDownPositionY = y; + mGestureStayedInTapRegion = true; + break; + + case MotionEvent.ACTION_POINTER_DOWN: + case MotionEvent.ACTION_POINTER_UP: + // Handle multi-point gestures. Keep min and max offset positions. + // Only activated for devices that correctly handle multi-touch. + if (mTextView.getContext().getPackageManager().hasSystemFeature( + PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) { + updateMinAndMaxOffsets(event); + } + break; + + case MotionEvent.ACTION_MOVE: + if (mGestureStayedInTapRegion) { + final float deltaX = event.getX() - mDownPositionX; + final float deltaY = event.getY() - mDownPositionY; + final float distanceSquared = deltaX * deltaX + deltaY * deltaY; + + final ViewConfiguration viewConfiguration = ViewConfiguration.get( + mTextView.getContext()); + int doubleTapTouchSlop = viewConfiguration.getScaledDoubleTapTouchSlop(); + + if (distanceSquared > doubleTapTouchSlop * doubleTapTouchSlop) { + mGestureStayedInTapRegion = false; + } + } + break; + + case MotionEvent.ACTION_UP: + mPreviousTapUpTime = SystemClock.uptimeMillis(); + break; + } + } + + /** + * @param event + */ + private void updateMinAndMaxOffsets(MotionEvent event) { + int pointerCount = event.getPointerCount(); + for (int index = 0; index < pointerCount; index++) { + int offset = mTextView.getOffsetForPosition(event.getX(index), event.getY(index)); + if (offset < mMinTouchOffset) mMinTouchOffset = offset; + if (offset > mMaxTouchOffset) mMaxTouchOffset = offset; + } + } + + public int getMinTouchOffset() { + return mMinTouchOffset; + } + + public int getMaxTouchOffset() { + return mMaxTouchOffset; + } + + public void resetTouchOffsets() { + mMinTouchOffset = mMaxTouchOffset = -1; + } + + /** + * @return true iff this controller is currently used to move the selection start. + */ + public boolean isSelectionStartDragged() { + return mStartHandle != null && mStartHandle.isDragging(); + } + + public void onTouchModeChanged(boolean isInTouchMode) { + if (!isInTouchMode) { + hide(); + } + } + + @Override + public void onDetached() { + final ViewTreeObserver observer = mTextView.getViewTreeObserver(); + observer.removeOnTouchModeChangeListener(this); + + if (mStartHandle != null) mStartHandle.onDetached(); + if (mEndHandle != null) mEndHandle.onDetached(); + } + } + + private class CorrectionHighlighter { + private final Path mPath = new Path(); + private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private int mStart, mEnd; + private long mFadingStartTime; + private RectF mTempRectF; + private final static int FADE_OUT_DURATION = 400; + + public CorrectionHighlighter() { + mPaint.setCompatibilityScaling(mTextView.getResources().getCompatibilityInfo(). + applicationScale); + mPaint.setStyle(Paint.Style.FILL); + } + + public void highlight(CorrectionInfo info) { + mStart = info.getOffset(); + mEnd = mStart + info.getNewText().length(); + mFadingStartTime = SystemClock.uptimeMillis(); + + if (mStart < 0 || mEnd < 0) { + stopAnimation(); + } + } + + public void draw(Canvas canvas, int cursorOffsetVertical) { + if (updatePath() && updatePaint()) { + if (cursorOffsetVertical != 0) { + canvas.translate(0, cursorOffsetVertical); + } + + canvas.drawPath(mPath, mPaint); + + if (cursorOffsetVertical != 0) { + canvas.translate(0, -cursorOffsetVertical); + } + invalidate(true); // TODO invalidate cursor region only + } else { + stopAnimation(); + invalidate(false); // TODO invalidate cursor region only + } + } + + private boolean updatePaint() { + final long duration = SystemClock.uptimeMillis() - mFadingStartTime; + if (duration > FADE_OUT_DURATION) return false; + + final float coef = 1.0f - (float) duration / FADE_OUT_DURATION; + final int highlightColorAlpha = Color.alpha(mTextView.mHighlightColor); + final int color = (mTextView.mHighlightColor & 0x00FFFFFF) + + ((int) (highlightColorAlpha * coef) << 24); + mPaint.setColor(color); + return true; + } + + private boolean updatePath() { + final Layout layout = mTextView.getLayout(); + if (layout == null) return false; + + // Update in case text is edited while the animation is run + final int length = mTextView.getText().length(); + int start = Math.min(length, mStart); + int end = Math.min(length, mEnd); + + mPath.reset(); + layout.getSelectionPath(start, end, mPath); + return true; + } + + private void invalidate(boolean delayed) { + if (mTextView.getLayout() == null) return; + + if (mTempRectF == null) mTempRectF = new RectF(); + mPath.computeBounds(mTempRectF, false); + + int left = mTextView.getCompoundPaddingLeft(); + int top = mTextView.getExtendedPaddingTop() + mTextView.getVerticalOffset(true); + + if (delayed) { + mTextView.postInvalidateOnAnimation( + left + (int) mTempRectF.left, top + (int) mTempRectF.top, + left + (int) mTempRectF.right, top + (int) mTempRectF.bottom); + } else { + mTextView.postInvalidate((int) mTempRectF.left, (int) mTempRectF.top, + (int) mTempRectF.right, (int) mTempRectF.bottom); + } + } + + private void stopAnimation() { + Editor.this.mCorrectionHighlighter = null; + } + } + + private static class ErrorPopup extends PopupWindow { + private boolean mAbove = false; + private final TextView mView; + private int mPopupInlineErrorBackgroundId = 0; + private int mPopupInlineErrorAboveBackgroundId = 0; + + ErrorPopup(TextView v, int width, int height) { + super(v, width, height); + mView = v; + // Make sure the TextView has a background set as it will be used the first time it is + // shown and positionned. Initialized with below background, which should have + // dimensions identical to the above version for this to work (and is more likely). + mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId, + com.android.internal.R.styleable.Theme_errorMessageBackground); + mView.setBackgroundResource(mPopupInlineErrorBackgroundId); + } + + void fixDirection(boolean above) { + mAbove = above; + + if (above) { + mPopupInlineErrorAboveBackgroundId = + getResourceId(mPopupInlineErrorAboveBackgroundId, + com.android.internal.R.styleable.Theme_errorMessageAboveBackground); + } else { + mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId, + com.android.internal.R.styleable.Theme_errorMessageBackground); + } + + mView.setBackgroundResource(above ? mPopupInlineErrorAboveBackgroundId : + mPopupInlineErrorBackgroundId); + } + + private int getResourceId(int currentId, int index) { + if (currentId == 0) { + TypedArray styledAttributes = mView.getContext().obtainStyledAttributes( + R.styleable.Theme); + currentId = styledAttributes.getResourceId(index, 0); + styledAttributes.recycle(); + } + return currentId; + } + + @Override + public void update(int x, int y, int w, int h, boolean force) { + super.update(x, y, w, h, force); + + boolean above = isAboveAnchor(); + if (above != mAbove) { + fixDirection(above); + } + } + } + + static class InputContentType { + int imeOptions = EditorInfo.IME_NULL; + String privateImeOptions; + CharSequence imeActionLabel; + int imeActionId; + Bundle extras; + OnEditorActionListener onEditorActionListener; + boolean enterDown; + } + + static class InputMethodState { + Rect mCursorRectInWindow = new Rect(); + RectF mTmpRectF = new RectF(); + float[] mTmpOffset = new float[2]; + ExtractedTextRequest mExtracting; + final ExtractedText mTmpExtracted = new ExtractedText(); + int mBatchEditNesting; + boolean mCursorChanged; + boolean mSelectionModeChanged; + boolean mContentChanged; + int mChangedStart, mChangedEnd, mChangedDelta; + } +} diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java index 586fdf4da501..806743536c96 100644 --- a/core/java/android/widget/RemoteViewsAdapter.java +++ b/core/java/android/widget/RemoteViewsAdapter.java @@ -477,8 +477,11 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback private static final String TAG = "FixedSizeRemoteViewsCache"; // The meta data related to all the RemoteViews, ie. count, is stable, etc. - private RemoteViewsMetaData mMetaData; - private RemoteViewsMetaData mTemporaryMetaData; + // The meta data objects are made final so that they can be locked on independently + // of the FixedSizeRemoteViewsCache. If we ever lock on both meta data objects, it is in + // the order mTemporaryMetaData followed by mMetaData. + private final RemoteViewsMetaData mMetaData; + private final RemoteViewsMetaData mTemporaryMetaData; // The cache/mapping of position to RemoteViewsMetaData. This set is guaranteed to be // greater than or equal to the set of RemoteViews. @@ -939,6 +942,10 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback * which wouldn't otherwise be possible. */ public void setVisibleRangeHint(int lowerBound, int upperBound) { + if (lowerBound < 0 || upperBound < 0) { + throw new RuntimeException("Attempted to set invalid range: lowerBound="+lowerBound + + "," + "upperBound="+upperBound); + } mVisibleWindowLowerBound = lowerBound; mVisibleWindowUpperBound = upperBound; } @@ -1072,12 +1079,20 @@ public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback // Re-request the new metadata (only after the notification to the factory) updateTemporaryMetaData(); + int newCount; + synchronized(mCache.getTemporaryMetaData()) { + newCount = mCache.getTemporaryMetaData().count; + } // Pre-load (our best guess of) the views which are currently visible in the AdapterView. // This mitigates flashing and flickering of loading views when a widget notifies that // its data has changed. for (int i = mVisibleWindowLowerBound; i <= mVisibleWindowUpperBound; i++) { - updateRemoteViews(i, false, false); + // Because temporary meta data is only ever modified from this thread (ie. + // mWorkerThread), it is safe to assume that count is a valid representation. + if (i < newCount) { + updateRemoteViews(i, false, false); + } } // Propagate the notification back to the base adapter diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 4bdb3e284cba..2a81f080eeb8 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -18,11 +18,8 @@ package android.widget; import android.R; import android.content.ClipData; -import android.content.ClipData.Item; import android.content.ClipboardManager; import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.content.res.CompatibilityInfo; import android.content.res.Resources; @@ -43,7 +40,6 @@ import android.os.Message; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; -import android.provider.Settings; import android.text.BoringLayout; import android.text.DynamicLayout; import android.text.Editable; @@ -57,7 +53,6 @@ import android.text.Selection; import android.text.SpanWatcher; import android.text.Spannable; import android.text.SpannableString; -import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.SpannedString; import android.text.StaticLayout; @@ -86,42 +81,31 @@ import android.text.method.TransformationMethod2; import android.text.method.WordIterator; import android.text.style.CharacterStyle; import android.text.style.ClickableSpan; -import android.text.style.EasyEditSpan; import android.text.style.ParagraphStyle; import android.text.style.SpellCheckSpan; -import android.text.style.SuggestionRangeSpan; import android.text.style.SuggestionSpan; -import android.text.style.TextAppearanceSpan; import android.text.style.URLSpan; import android.text.style.UpdateAppearance; import android.text.util.Linkify; import android.util.AttributeSet; -import android.util.DisplayMetrics; import android.util.FloatMath; import android.util.Log; import android.util.TypedValue; import android.view.ActionMode; -import android.view.ActionMode.Callback; -import android.view.DisplayList; import android.view.DragEvent; import android.view.Gravity; import android.view.HapticFeedbackConstants; -import android.view.HardwareCanvas; import android.view.KeyCharacterMap; import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewDebug; -import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; -import android.view.ViewParent; import android.view.ViewRootImpl; import android.view.ViewTreeObserver; -import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -136,10 +120,8 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.view.textservice.SpellCheckerSubtype; import android.view.textservice.TextServicesManager; -import android.widget.AdapterView.OnItemClickListener; import android.widget.RemoteViews.RemoteView; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastMath; import com.android.internal.widget.EditableInputConnection; @@ -147,11 +129,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.lang.ref.WeakReference; -import java.text.BreakIterator; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; import java.util.Locale; /** @@ -267,24 +245,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private static final int PIXELS = 2; private static final RectF TEMP_RECTF = new RectF(); - private static final float[] TEMP_POSITION = new float[2]; // XXX should be much larger private static final int VERY_WIDE = 1024*1024; - private static final int BLINK = 500; private static final int ANIMATED_SCROLL_GAP = 250; private static final InputFilter[] NO_FILTERS = new InputFilter[0]; private static final Spanned EMPTY_SPANNED = new SpannedString(""); - private static int DRAG_SHADOW_MAX_TEXT_LENGTH = 20; private static final int CHANGE_WATCHER_PRIORITY = 100; // New state used to change background based on whether this TextView is multiline. private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline }; // System wide time for last cut or copy action. - private static long LAST_CUT_OR_COPY_TIME; + static long LAST_CUT_OR_COPY_TIME; private int mCurrentAlpha = 255; @@ -316,7 +291,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mDrawableHeightStart, mDrawableHeightEnd; int mDrawablePadding; } - private Drawables mDrawables; + Drawables mDrawables; private CharWrapper mCharWrapper; @@ -404,23 +379,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // It is possible to have a selection even when mEditor is null (programmatically set, like when // a link is pressed). These highlight-related fields do not go in mEditor. - private int mHighlightColor = 0x6633B5E5; + int mHighlightColor = 0x6633B5E5; private Path mHighlightPath; private final Paint mHighlightPaint; private boolean mHighlightPathBogus = true; // Although these fields are specific to editable text, they are not added to Editor because // they are defined by the TextView's style and are theme-dependent. - private int mCursorDrawableRes; + int mCursorDrawableRes; // These four fields, could be moved to Editor, since we know their default values and we // could condition the creation of the Editor to a non standard value. This is however // brittle since the hardcoded values here (such as // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the // default style is modified. - private int mTextSelectHandleLeftRes; - private int mTextSelectHandleRightRes; - private int mTextSelectHandleRes; - private int mTextEditSuggestionItemLayout; + int mTextSelectHandleLeftRes; + int mTextSelectHandleRightRes; + int mTextSelectHandleRes; + int mTextEditSuggestionItemLayout; /** * EditText specific data, created on demand when one of the Editor fields is used. @@ -826,26 +801,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case com.android.internal.R.styleable.TextView_imeOptions: createEditorIfNeeded("IME options specified in constructor"); - if (getEditor().mInputContentType == null) { - getEditor().mInputContentType = new InputContentType(); - } + getEditor().createInputContentTypeIfNeeded(); getEditor().mInputContentType.imeOptions = a.getInt(attr, getEditor().mInputContentType.imeOptions); break; case com.android.internal.R.styleable.TextView_imeActionLabel: createEditorIfNeeded("IME action label specified in constructor"); - if (getEditor().mInputContentType == null) { - getEditor().mInputContentType = new InputContentType(); - } + getEditor().createInputContentTypeIfNeeded(); getEditor().mInputContentType.imeActionLabel = a.getText(attr); break; case com.android.internal.R.styleable.TextView_imeActionId: createEditorIfNeeded("IME action id specified in constructor"); - if (getEditor().mInputContentType == null) { - getEditor().mInputContentType = new InputContentType(); - } + getEditor().createInputContentTypeIfNeeded(); getEditor().mInputContentType.imeActionId = a.getInt(attr, getEditor().mInputContentType.imeActionId); break; @@ -1135,7 +1104,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener setClickable(clickable); setLongClickable(longClickable); - prepareCursorControllers(); + if (mEditor != null) mEditor.prepareCursorControllers(); } private void setTypefaceByIndex(int typefaceIndex, int styleIndex) { @@ -1216,11 +1185,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } // Will change text color - if (mEditor != null) getEditor().invalidateTextDisplayList(); - prepareCursorControllers(); + if (mEditor != null) { + getEditor().invalidateTextDisplayList(); + getEditor().prepareCursorControllers(); - // start or stop the cursor blinking as appropriate - makeBlink(); + // start or stop the cursor blinking as appropriate + getEditor().makeBlink(); + } } /** @@ -1412,7 +1383,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener fixFocusableAndClickableSettings(); // SelectionModifierCursorController depends on textCanBeSelected, which depends on mMovement - prepareCursorControllers(); + if (mEditor != null) getEditor().prepareCursorControllers(); } } @@ -3250,7 +3221,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } // SelectionModifierCursorController depends on textCanBeSelected, which depends on text - prepareCursorControllers(); + if (mEditor != null) getEditor().prepareCursorControllers(); } /** @@ -3366,12 +3337,37 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mHint; } + boolean isSingleLine() { + return mSingleLine; + } + private static boolean isMultilineInputType(int type) { return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); } /** + * Removes the suggestion spans. + */ + CharSequence removeSuggestionSpans(CharSequence text) { + if (text instanceof Spanned) { + Spannable spannable; + if (text instanceof Spannable) { + spannable = (Spannable) text; + } else { + spannable = new SpannableString(text); + text = spannable; + } + + SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); + for (int i = 0; i < spans.length; i++) { + spannable.removeSpan(spans[i]); + } + } + return text; + } + + /** * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)}, * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL} @@ -3543,9 +3539,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public void setImeOptions(int imeOptions) { createEditorIfNeeded("IME options specified"); - if (getEditor().mInputContentType == null) { - getEditor().mInputContentType = new InputContentType(); - } + getEditor().createInputContentTypeIfNeeded(); getEditor().mInputContentType.imeOptions = imeOptions; } @@ -3572,9 +3566,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public void setImeActionLabel(CharSequence label, int actionId) { createEditorIfNeeded("IME action label specified"); - if (getEditor().mInputContentType == null) { - getEditor().mInputContentType = new InputContentType(); - } + getEditor().createInputContentTypeIfNeeded(); getEditor().mInputContentType.imeActionLabel = label; getEditor().mInputContentType.imeActionId = actionId; } @@ -3611,9 +3603,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public void setOnEditorActionListener(OnEditorActionListener l) { createEditorIfNeeded("Editor action listener set"); - if (getEditor().mInputContentType == null) { - getEditor().mInputContentType = new InputContentType(); - } + getEditor().createInputContentTypeIfNeeded(); getEditor().mInputContentType.onEditorActionListener = l; } @@ -3638,7 +3628,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @see #setOnEditorActionListener */ public void onEditorAction(int actionCode) { - final InputContentType ict = mEditor == null ? null : getEditor().mInputContentType; + final Editor.InputContentType ict = mEditor == null ? null : getEditor().mInputContentType; if (ict != null) { if (ict.onEditorActionListener != null) { if (ict.onEditorActionListener.onEditorAction(this, @@ -3710,8 +3700,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public void setPrivateImeOptions(String type) { createEditorIfNeeded("Private IME option set"); - if (getEditor().mInputContentType == null) - getEditor().mInputContentType = new InputContentType(); + getEditor().createInputContentTypeIfNeeded(); getEditor().mInputContentType.privateImeOptions = type; } @@ -3740,8 +3729,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void setInputExtras(int xmlResId) throws XmlPullParserException, IOException { createEditorIfNeeded("Input extra set"); XmlResourceParser parser = getResources().getXml(xmlResId); - if (getEditor().mInputContentType == null) - getEditor().mInputContentType = new InputContentType(); + getEditor().createInputContentTypeIfNeeded(); getEditor().mInputContentType.extras = new Bundle(); getResources().parseBundleExtras(parser, getEditor().mInputContentType.extras); } @@ -3761,7 +3749,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener createEditorIfNeeded("get Input extra"); if (getEditor().mInputContentType == null) { if (!create) return null; - getEditor().mInputContentType = new InputContentType(); + getEditor().createInputContentTypeIfNeeded(); } if (getEditor().mInputContentType.extras == null) { if (!create) return null; @@ -3811,142 +3799,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ public void setError(CharSequence error, Drawable icon) { createEditorIfNeeded("setError"); - error = TextUtils.stringOrSpannedString(error); - - getEditor().mError = error; - getEditor().mErrorWasChanged = true; - final Drawables dr = mDrawables; - if (dr != null) { - switch (getResolvedLayoutDirection()) { - default: - case LAYOUT_DIRECTION_LTR: - setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop, icon, - dr.mDrawableBottom); - break; - case LAYOUT_DIRECTION_RTL: - setCompoundDrawables(icon, dr.mDrawableTop, dr.mDrawableRight, - dr.mDrawableBottom); - break; - } - } else { - setCompoundDrawables(null, null, icon, null); - } - - if (error == null) { - if (getEditor().mErrorPopup != null) { - if (getEditor().mErrorPopup.isShowing()) { - getEditor().mErrorPopup.dismiss(); - } - - getEditor().mErrorPopup = null; - } - } else { - if (isFocused()) { - showError(); - } - } - } - - private void showError() { - if (getWindowToken() == null) { - getEditor().mShowErrorAfterAttach = true; - return; - } - - if (getEditor().mErrorPopup == null) { - LayoutInflater inflater = LayoutInflater.from(getContext()); - final TextView err = (TextView) inflater.inflate( - com.android.internal.R.layout.textview_hint, null); - - final float scale = getResources().getDisplayMetrics().density; - getEditor().mErrorPopup = new ErrorPopup(err, (int) (200 * scale + 0.5f), (int) (50 * scale + 0.5f)); - getEditor().mErrorPopup.setFocusable(false); - // The user is entering text, so the input method is needed. We - // don't want the popup to be displayed on top of it. - getEditor().mErrorPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); - } - - TextView tv = (TextView) getEditor().mErrorPopup.getContentView(); - chooseSize(getEditor().mErrorPopup, getEditor().mError, tv); - tv.setText(getEditor().mError); - - getEditor().mErrorPopup.showAsDropDown(this, getErrorX(), getErrorY()); - getEditor().mErrorPopup.fixDirection(getEditor().mErrorPopup.isAboveAnchor()); - } - - /** - * Returns the Y offset to make the pointy top of the error point - * at the middle of the error icon. - */ - private int getErrorX() { - /* - * The "25" is the distance between the point and the right edge - * of the background - */ - final float scale = getResources().getDisplayMetrics().density; - - final Drawables dr = mDrawables; - return getWidth() - getEditor().mErrorPopup.getWidth() - getPaddingRight() - - (dr != null ? dr.mDrawableSizeRight : 0) / 2 + (int) (25 * scale + 0.5f); - } - - /** - * Returns the Y offset to make the pointy top of the error point - * at the bottom of the error icon. - */ - private int getErrorY() { - /* - * Compound, not extended, because the icon is not clipped - * if the text height is smaller. - */ - final int compoundPaddingTop = getCompoundPaddingTop(); - int vspace = mBottom - mTop - getCompoundPaddingBottom() - compoundPaddingTop; - - final Drawables dr = mDrawables; - int icontop = compoundPaddingTop + - (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2; - - /* - * The "2" is the distance between the point and the top edge - * of the background. - */ - final float scale = getResources().getDisplayMetrics().density; - return icontop + (dr != null ? dr.mDrawableHeightRight : 0) - getHeight() - - (int) (2 * scale + 0.5f); - } - - private void hideError() { - if (getEditor().mErrorPopup != null) { - if (getEditor().mErrorPopup.isShowing()) { - getEditor().mErrorPopup.dismiss(); - } - } - - getEditor().mShowErrorAfterAttach = false; - } - - private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) { - int wid = tv.getPaddingLeft() + tv.getPaddingRight(); - int ht = tv.getPaddingTop() + tv.getPaddingBottom(); - - int defaultWidthInPixels = getResources().getDimensionPixelSize( - com.android.internal.R.dimen.textview_error_popup_default_width); - Layout l = new StaticLayout(text, tv.getPaint(), defaultWidthInPixels, - Layout.Alignment.ALIGN_NORMAL, 1, 0, true); - float max = 0; - for (int i = 0; i < l.getLineCount(); i++) { - max = Math.max(max, l.getLineWidth(i)); - } - - /* - * Now set the popup size to be big enough for the text plus the border capped - * to DEFAULT_MAX_POPUP_WIDTH - */ - pop.setWidth(wid + (int) Math.ceil(max)); - pop.setHeight(ht + l.getHeight()); + getEditor().setError(error, icon); } - @Override protected boolean setFrame(int l, int t, int r, int b) { boolean result = super.setFrame(l, t, r, b); @@ -4009,7 +3864,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener ///////////////////////////////////////////////////////////////////////// - private int getVerticalOffset(boolean forceNormal) { + int getVerticalOffset(boolean forceNormal) { int voffset = 0; final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; @@ -4071,7 +3926,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return voffset; } - private void invalidateCursorPath() { + void invalidateCursorPath() { if (mHighlightPathBogus) { invalidateCursor(); } else { @@ -4114,7 +3969,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - private void invalidateCursor() { + void invalidateCursor() { int where = getSelectionEnd(); invalidateCursor(where, where, where); @@ -4130,8 +3985,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Invalidates the region of text enclosed between the start and end text offsets. - * - * @hide */ void invalidateRegion(int start, int end, boolean invalidateCursor) { if (mLayout == null) { @@ -4237,15 +4090,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // - onFocusChanged cannot start it when focus is given to a view with selected text (after // a screen rotation) since layout is not yet initialized at that point. if (mEditor != null && getEditor().mCreatedWithASelection) { - startSelectionActionMode(); + getEditor().startSelectionActionMode(); getEditor().mCreatedWithASelection = false; } // Phone specific code (there is no ExtractEditText on tablets). // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can // not be set. Do the test here instead. - if (this instanceof ExtractEditText && hasSelection()) { - startSelectionActionMode(); + if (this instanceof ExtractEditText && hasSelection() && mEditor != null) { + getEditor().startSelectionActionMode(); } getViewTreeObserver().removeOnPreDrawListener(this); @@ -4260,11 +4113,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mTemporaryDetach = false; - if (mEditor != null && getEditor().mShowErrorAfterAttach) { - showError(); - getEditor().mShowErrorAfterAttach = false; - } - // Resolve drawables as the layout direction has been resolved resolveDrawables(); @@ -4495,7 +4343,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener setText(getText(), selectable ? BufferType.SPANNABLE : BufferType.NORMAL); // Called by setText above, but safer in case of future code changes - prepareCursorControllers(); + getEditor().prepareCursorControllers(); } @Override @@ -4536,8 +4384,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final int selEnd = getSelectionEnd(); if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) { if (selStart == selEnd) { - if (mEditor != null && isCursorVisible() && - (SystemClock.uptimeMillis() - getEditor().mShowCursor) % (2 * BLINK) < BLINK) { + if (mEditor != null && getEditor().isCursorVisible() && + (SystemClock.uptimeMillis() - getEditor().mShowCursor) % + (2 * Editor.BLINK) < Editor.BLINK) { if (mHighlightPathBogus) { if (mHighlightPath == null) mHighlightPath = new Path(); mHighlightPath.reset(); @@ -4730,14 +4579,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Path highlight = getUpdatedHighlightPath(); if (mEditor != null) { - getEditor().onDraw(canvas, layout, highlight, cursorOffsetVertical); + getEditor().onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); } else { layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); + } - if (mMarquee != null && mMarquee.shouldDrawGhost()) { - canvas.translate((int) mMarquee.getGhostOffset(), 0.0f); - layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); - } + if (mMarquee != null && mMarquee.shouldDrawGhost()) { + canvas.translate((int) mMarquee.getGhostOffset(), 0.0f); + layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); } canvas.restore(); @@ -4853,7 +4702,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * @hide - * @param offsetRequired */ @Override protected int getFadeTop(boolean offsetRequired) { @@ -4871,7 +4719,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * @hide - * @param offsetRequired */ @Override protected int getFadeHeight(boolean offsetRequired) { @@ -5252,9 +5099,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { if (onCheckIsTextEditor() && isEnabled()) { - if (getEditor().mInputMethodState == null) { - getEditor().mInputMethodState = new InputMethodState(); - } + getEditor().createInputMethodStateIfNeeded(); outAttrs.inputType = getInputType(); if (getEditor().mInputContentType != null) { outAttrs.imeOptions = getEditor().mInputContentType.imeOptions; @@ -5307,122 +5152,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * based on the information in <var>request</var> in to <var>outText</var>. * @return Returns true if the text was successfully extracted, else false. */ - public boolean extractText(ExtractedTextRequest request, - ExtractedText outText) { - return extractTextInternal(request, EXTRACT_UNKNOWN, EXTRACT_UNKNOWN, - EXTRACT_UNKNOWN, outText); + public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { + createEditorIfNeeded("extractText"); + return getEditor().extractText(request, outText); } - - static final int EXTRACT_NOTHING = -2; - static final int EXTRACT_UNKNOWN = -1; - - boolean extractTextInternal(ExtractedTextRequest request, - int partialStartOffset, int partialEndOffset, int delta, - ExtractedText outText) { - final CharSequence content = mText; - if (content != null) { - if (partialStartOffset != EXTRACT_NOTHING) { - final int N = content.length(); - if (partialStartOffset < 0) { - outText.partialStartOffset = outText.partialEndOffset = -1; - partialStartOffset = 0; - partialEndOffset = N; - } else { - // Now use the delta to determine the actual amount of text - // we need. - partialEndOffset += delta; - // Adjust offsets to ensure we contain full spans. - if (content instanceof Spanned) { - Spanned spanned = (Spanned)content; - Object[] spans = spanned.getSpans(partialStartOffset, - partialEndOffset, ParcelableSpan.class); - int i = spans.length; - while (i > 0) { - i--; - int j = spanned.getSpanStart(spans[i]); - if (j < partialStartOffset) partialStartOffset = j; - j = spanned.getSpanEnd(spans[i]); - if (j > partialEndOffset) partialEndOffset = j; - } - } - outText.partialStartOffset = partialStartOffset; - outText.partialEndOffset = partialEndOffset - delta; - if (partialStartOffset > N) { - partialStartOffset = N; - } else if (partialStartOffset < 0) { - partialStartOffset = 0; - } - if (partialEndOffset > N) { - partialEndOffset = N; - } else if (partialEndOffset < 0) { - partialEndOffset = 0; - } - } - if ((request.flags&InputConnection.GET_TEXT_WITH_STYLES) != 0) { - outText.text = content.subSequence(partialStartOffset, - partialEndOffset); - } else { - outText.text = TextUtils.substring(content, partialStartOffset, - partialEndOffset); - } - } else { - outText.partialStartOffset = 0; - outText.partialEndOffset = 0; - outText.text = ""; - } - outText.flags = 0; - if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) { - outText.flags |= ExtractedText.FLAG_SELECTING; - } - if (mSingleLine) { - outText.flags |= ExtractedText.FLAG_SINGLE_LINE; - } - outText.startOffset = 0; - outText.selectionStart = getSelectionStart(); - outText.selectionEnd = getSelectionEnd(); - return true; - } - return false; - } - - boolean reportExtractedText() { - final InputMethodState ims = getEditor().mInputMethodState; - if (ims != null) { - final boolean contentChanged = ims.mContentChanged; - if (contentChanged || ims.mSelectionModeChanged) { - ims.mContentChanged = false; - ims.mSelectionModeChanged = false; - final ExtractedTextRequest req = ims.mExtracting; - if (req != null) { - InputMethodManager imm = InputMethodManager.peekInstance(); - if (imm != null) { - if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Retrieving extracted start=" - + ims.mChangedStart + " end=" + ims.mChangedEnd - + " delta=" + ims.mChangedDelta); - if (ims.mChangedStart < 0 && !contentChanged) { - ims.mChangedStart = EXTRACT_NOTHING; - } - if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd, - ims.mChangedDelta, ims.mTmpExtracted)) { - if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Reporting extracted start=" - + ims.mTmpExtracted.partialStartOffset - + " end=" + ims.mTmpExtracted.partialEndOffset - + ": " + ims.mTmpExtracted.text); - imm.updateExtractedText(this, req.token, ims.mTmpExtracted); - ims.mChangedStart = EXTRACT_UNKNOWN; - ims.mChangedEnd = EXTRACT_UNKNOWN; - ims.mChangedDelta = 0; - ims.mContentChanged = false; - return true; - } - } - } - } - } - return false; - } - /** * This is used to remove all style-impacting spans from text before new * extracted text is being replaced into it, so that we don't have any @@ -5436,7 +5170,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener spannable.removeSpan(spans[i]); } } - + /** * Apply to this text view the given extracted text, as previously * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. @@ -5492,7 +5226,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // This would stop a possible selection mode, but no such mode is started in case // extracted mode will start. Some text is selected though, and will trigger an action mode // in the extracted view. - hideControllers(); + getEditor().hideControllers(); } /** @@ -5518,87 +5252,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @param info The auto correct info about the text that was corrected. */ public void onCommitCorrection(CorrectionInfo info) { - if (mEditor == null) return; - if (getEditor().mCorrectionHighlighter == null) { - getEditor().mCorrectionHighlighter = new CorrectionHighlighter(); - } else { - getEditor().mCorrectionHighlighter.invalidate(false); - } - - getEditor().mCorrectionHighlighter.highlight(info); + if (mEditor != null) getEditor().onCommitCorrection(info); } public void beginBatchEdit() { - if (mEditor == null) return; - getEditor().mInBatchEditControllers = true; - final InputMethodState ims = getEditor().mInputMethodState; - if (ims != null) { - int nesting = ++ims.mBatchEditNesting; - if (nesting == 1) { - ims.mCursorChanged = false; - ims.mChangedDelta = 0; - if (ims.mContentChanged) { - // We already have a pending change from somewhere else, - // so turn this into a full update. - ims.mChangedStart = 0; - ims.mChangedEnd = mText.length(); - } else { - ims.mChangedStart = EXTRACT_UNKNOWN; - ims.mChangedEnd = EXTRACT_UNKNOWN; - ims.mContentChanged = false; - } - onBeginBatchEdit(); - } - } + if (mEditor != null) getEditor().beginBatchEdit(); } public void endBatchEdit() { - if (mEditor == null) return; - getEditor().mInBatchEditControllers = false; - final InputMethodState ims = getEditor().mInputMethodState; - if (ims != null) { - int nesting = --ims.mBatchEditNesting; - if (nesting == 0) { - finishBatchEdit(ims); - } - } - } - - void ensureEndedBatchEdit() { - final InputMethodState ims = getEditor().mInputMethodState; - if (ims != null && ims.mBatchEditNesting != 0) { - ims.mBatchEditNesting = 0; - finishBatchEdit(ims); - } - } - - void finishBatchEdit(final InputMethodState ims) { - onEndBatchEdit(); - - if (ims.mContentChanged || ims.mSelectionModeChanged) { - updateAfterEdit(); - reportExtractedText(); - } else if (ims.mCursorChanged) { - // Cheezy way to get us to report the current cursor location. - invalidateCursor(); - } - } - - void updateAfterEdit() { - invalidate(); - int curs = getSelectionStart(); - - if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { - registerForPreDraw(); - } - - if (curs >= 0) { - mHighlightPathBogus = true; - makeBlink(); - bringPointIntoView(curs); - } - - checkForResize(); + if (mEditor != null) getEditor().endBatchEdit(); } /** @@ -5644,7 +5306,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mBoring = mHintBoring = null; // Since it depends on the value of mLayout - prepareCursorControllers(); + if (mEditor != null) getEditor().prepareCursorControllers(); } /** @@ -5826,7 +5488,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } // CursorControllers need a non-null mLayout - prepareCursorControllers(); + if (mEditor != null) getEditor().prepareCursorControllers(); } private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, @@ -6638,11 +6300,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener r.bottom += verticalOffset; } - private int viewportToContentHorizontalOffset() { + int viewportToContentHorizontalOffset() { return getCompoundPaddingLeft() - mScrollX; } - private int viewportToContentVerticalOffset() { + int viewportToContentVerticalOffset() { int offset = getExtendedPaddingTop() - mScrollY; if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { offset += getVerticalOffset(false); @@ -6855,18 +6517,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener getEditor().mCursorVisible = visible; invalidate(); - makeBlink(); + getEditor().makeBlink(); // InsertionPointCursorController depends on mCursorVisible - prepareCursorControllers(); + getEditor().prepareCursorControllers(); } } - private boolean isCursorVisible() { - // The default value is true, even when there is no associated Editor - return mEditor == null ? true : (getEditor().mCursorVisible && isTextEditable()); - } - private boolean canMarquee() { int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight()); return width > 0 && (mLayout.getLineWidth(0) > width || @@ -7049,12 +6706,29 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + void updateAfterEdit() { + invalidate(); + int curs = getSelectionStart(); + + if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { + registerForPreDraw(); + } + + if (curs >= 0) { + mHighlightPathBogus = true; + if (mEditor != null) getEditor().makeBlink(); + bringPointIntoView(curs); + } + + checkForResize(); + } + /** * Not private so it can be called from an inner class without going * through a thunk. */ void handleTextChanged(CharSequence buffer, int start, int before, int after) { - final InputMethodState ims = mEditor == null ? null : getEditor().mInputMethodState; + final Editor.InputMethodState ims = mEditor == null ? null : getEditor().mInputMethodState; if (ims == null || ims.mBatchEditNesting == 0) { updateAfterEdit(); } @@ -7085,7 +6759,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean selChanged = false; int newSelStart=-1, newSelEnd=-1; - final InputMethodState ims = mEditor == null ? null : getEditor().mInputMethodState; + final Editor.InputMethodState ims = mEditor == null ? null : getEditor().mInputMethodState; if (what == Selection.SELECTION_END) { selChanged = true; @@ -7094,7 +6768,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (oldStart >= 0 || newStart >= 0) { invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); registerForPreDraw(); - makeBlink(); + if (mEditor != null) getEditor().makeBlink(); } } @@ -7186,20 +6860,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * Create new SpellCheckSpans on the modified region. - */ - private void updateSpellCheckSpans(int start, int end, boolean createSpellChecker) { - if (isTextEditable() && isSuggestionsEnabled() && !(this instanceof ExtractEditText)) { - if (getEditor().mSpellChecker == null && createSpellChecker) { - getEditor().mSpellChecker = new SpellChecker(this); - } - if (getEditor().mSpellChecker != null) { - getEditor().mSpellChecker.spellCheck(start, end); - } - } - } - - /** * @hide */ @Override @@ -7219,7 +6879,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Because of View recycling in ListView, there is no easy way to know when a TextView with // selection becomes visible again. Until a better solution is found, stop text selection // mode (if any) as soon as this TextView is recycled. - if (mEditor != null) hideControllers(); + if (mEditor != null) getEditor().hideControllers(); } @Override @@ -7269,7 +6929,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); if (mEditor != null && visibility != VISIBLE) { - hideControllers(); + getEditor().hideControllers(); } } @@ -7350,31 +7010,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener handled |= imm != null && imm.showSoftInput(this, 0); } - boolean selectAllGotFocus = getEditor().mSelectAllOnFocus && didTouchFocusSelect(); - hideControllers(); - if (!selectAllGotFocus && mText.length() > 0) { - // Move cursor - final int offset = getOffsetForPosition(event.getX(), event.getY()); - Selection.setSelection((Spannable) mText, offset); - if (getEditor().mSpellChecker != null) { - // When the cursor moves, the word that was typed may need spell check - getEditor().mSpellChecker.onSelectionChanged(); - } - if (!extractedTextModeWillBeStarted()) { - if (isCursorInsideEasyCorrectionSpan()) { - getEditor().mShowSuggestionRunnable = new Runnable() { - public void run() { - showSuggestions(); - } - }; - // removeCallbacks is performed on every touch - postDelayed(getEditor().mShowSuggestionRunnable, - ViewConfiguration.getDoubleTapTimeout()); - } else if (hasInsertionController()) { - getInsertionController().show(); - } - } - } + // The above condition ensures that the mEditor is not null + getEditor().onTouchUpEvent(event); handled = true; } @@ -7387,53 +7024,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return superResult; } - /** - * @return <code>true</code> if the cursor/current selection overlaps a {@link SuggestionSpan}. - */ - private boolean isCursorInsideSuggestionSpan() { - if (!(mText instanceof Spannable)) return false; - - SuggestionSpan[] suggestionSpans = ((Spannable) mText).getSpans(getSelectionStart(), - getSelectionEnd(), SuggestionSpan.class); - return (suggestionSpans.length > 0); - } - - /** - * @return <code>true</code> if the cursor is inside an {@link SuggestionSpan} with - * {@link SuggestionSpan#FLAG_EASY_CORRECT} set. - */ - private boolean isCursorInsideEasyCorrectionSpan() { - Spannable spannable = (Spannable) mText; - SuggestionSpan[] suggestionSpans = spannable.getSpans(getSelectionStart(), - getSelectionEnd(), SuggestionSpan.class); - for (int i = 0; i < suggestionSpans.length; i++) { - if ((suggestionSpans[i].getFlags() & SuggestionSpan.FLAG_EASY_CORRECT) != 0) { - return true; - } - } - return false; - } - - /** - * Downgrades to simple suggestions all the easy correction spans that are not a spell check - * span. - */ - private void downgradeEasyCorrectionSpans() { - if (mText instanceof Spannable) { - Spannable spannable = (Spannable) mText; - SuggestionSpan[] suggestionSpans = spannable.getSpans(0, - spannable.length(), SuggestionSpan.class); - for (int i = 0; i < suggestionSpans.length; i++) { - int flags = suggestionSpans[i].getFlags(); - if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 - && (flags & SuggestionSpan.FLAG_MISSPELLED) == 0) { - flags &= ~SuggestionSpan.FLAG_EASY_CORRECT; - suggestionSpans[i].setFlags(flags); - } - } - } - } - @Override public boolean onGenericMotionEvent(MotionEvent event) { if (mMovement != null && mText instanceof Spannable && mLayout != null) { @@ -7450,44 +7040,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return super.onGenericMotionEvent(event); } - private void prepareCursorControllers() { - if (mEditor == null) return; - - boolean windowSupportsHandles = false; - - ViewGroup.LayoutParams params = getRootView().getLayoutParams(); - if (params instanceof WindowManager.LayoutParams) { - WindowManager.LayoutParams windowParams = (WindowManager.LayoutParams) params; - windowSupportsHandles = windowParams.type < WindowManager.LayoutParams.FIRST_SUB_WINDOW - || windowParams.type > WindowManager.LayoutParams.LAST_SUB_WINDOW; - } - - getEditor().mInsertionControllerEnabled = windowSupportsHandles && isCursorVisible() && mLayout != null; - getEditor().mSelectionControllerEnabled = windowSupportsHandles && textCanBeSelected() && - mLayout != null; - - if (!getEditor().mInsertionControllerEnabled) { - hideInsertionPointCursorController(); - if (getEditor().mInsertionPointCursorController != null) { - getEditor().mInsertionPointCursorController.onDetached(); - getEditor().mInsertionPointCursorController = null; - } - } - - if (!getEditor().mSelectionControllerEnabled) { - stopSelectionActionMode(); - if (getEditor().mSelectionModifierCursorController != null) { - getEditor().mSelectionModifierCursorController.onDetached(); - getEditor().mSelectionModifierCursorController = null; - } - } - } - /** * @return True iff this TextView contains a text that can be edited, or if this is * a selectable TextView. */ - private boolean isTextEditable() { + boolean isTextEditable() { return mText instanceof Editable && onCheckIsTextEditor() && isEnabled(); } @@ -7522,32 +7079,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mScroller = s; } - /** - * @return True when the TextView isFocused and has a valid zero-length selection (cursor). - */ - private boolean shouldBlink() { - if (mEditor == null || !isCursorVisible() || !isFocused()) return false; - - final int start = getSelectionStart(); - if (start < 0) return false; - - final int end = getSelectionEnd(); - if (end < 0) return false; - - return start == end; - } - - private void makeBlink() { - if (shouldBlink()) { - getEditor().mShowCursor = SystemClock.uptimeMillis(); - if (getEditor().mBlink == null) getEditor().mBlink = new Blink(this); - getEditor().mBlink.removeCallbacks(getEditor().mBlink); - getEditor().mBlink.postAtTime(getEditor().mBlink, getEditor().mShowCursor + BLINK); - } else { - if (mEditor != null && getEditor().mBlink != null) getEditor().mBlink.removeCallbacks(getEditor().mBlink); - } - } - @Override protected float getLeftFadingEdgeStrength() { if (mCurrentAlpha <= ViewConfiguration.ALPHA_THRESHOLD_INT) return 0.0f; @@ -7726,10 +7257,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have - * a selection controller (see {@link #prepareCursorControllers()}), but this is not sufficient. + * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not sufficient. */ private boolean canSelectText() { - return hasSelectionController() && mText.length() != 0; + return mText.length() != 0 && mEditor != null && getEditor().hasSelectionController(); } /** @@ -7738,7 +7269,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * * See also {@link #canSelectText()}. */ - private boolean textCanBeSelected() { + boolean textCanBeSelected() { // prepareCursorController() relies on this method. // If you change this condition, make sure prepareCursorController is called anywhere // the value of this condition might be changed. @@ -7746,112 +7277,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return isTextEditable() || (isTextSelectable() && mText instanceof Spannable && isEnabled()); } - private boolean canCut() { - if (hasPasswordTransformationMethod()) { - return false; - } - - if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null && getEditor().mKeyListener != null) { - return true; - } - - return false; - } - - private boolean canCopy() { - if (hasPasswordTransformationMethod()) { - return false; - } - - if (mText.length() > 0 && hasSelection()) { - return true; - } - - return false; - } - - private boolean canPaste() { - return (mText instanceof Editable && - mEditor != null && getEditor().mKeyListener != null && - getSelectionStart() >= 0 && - getSelectionEnd() >= 0 && - ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)). - hasPrimaryClip()); - } - - private boolean selectAll() { - final int length = mText.length(); - Selection.setSelection((Spannable) mText, 0, length); - return length > 0; - } - - /** - * Adjusts selection to the word under last touch offset. - * Return true if the operation was successfully performed. - */ - private boolean selectCurrentWord() { - if (!canSelectText()) { - return false; - } - - if (hasPasswordTransformationMethod()) { - // Always select all on a password field. - // Cut/copy menu entries are not available for passwords, but being able to select all - // is however useful to delete or paste to replace the entire content. - return selectAll(); - } - - int inputType = getInputType(); - int klass = inputType & InputType.TYPE_MASK_CLASS; - int variation = inputType & InputType.TYPE_MASK_VARIATION; - - // Specific text field types: select the entire text for these - if (klass == InputType.TYPE_CLASS_NUMBER || - klass == InputType.TYPE_CLASS_PHONE || - klass == InputType.TYPE_CLASS_DATETIME || - variation == InputType.TYPE_TEXT_VARIATION_URI || - variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS || - variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS || - variation == InputType.TYPE_TEXT_VARIATION_FILTER) { - return selectAll(); - } - - long lastTouchOffsets = getLastTouchOffsets(); - final int minOffset = TextUtils.unpackRangeStartFromLong(lastTouchOffsets); - final int maxOffset = TextUtils.unpackRangeEndFromLong(lastTouchOffsets); - - // Safety check in case standard touch event handling has been bypassed - if (minOffset < 0 || minOffset >= mText.length()) return false; - if (maxOffset < 0 || maxOffset >= mText.length()) return false; - - int selectionStart, selectionEnd; - - // If a URLSpan (web address, email, phone...) is found at that position, select it. - URLSpan[] urlSpans = ((Spanned) mText).getSpans(minOffset, maxOffset, URLSpan.class); - if (urlSpans.length >= 1) { - URLSpan urlSpan = urlSpans[0]; - selectionStart = ((Spanned) mText).getSpanStart(urlSpan); - selectionEnd = ((Spanned) mText).getSpanEnd(urlSpan); - } else { - final WordIterator wordIterator = getWordIterator(); - wordIterator.setCharSequence(mText, minOffset, maxOffset); - - selectionStart = wordIterator.getBeginning(minOffset); - selectionEnd = wordIterator.getEnd(maxOffset); - - if (selectionStart == BreakIterator.DONE || selectionEnd == BreakIterator.DONE || - selectionStart == selectionEnd) { - // Possible when the word iterator does not properly handle the text's language - long range = getCharRange(minOffset); - selectionStart = TextUtils.unpackRangeStartFromLong(range); - selectionEnd = TextUtils.unpackRangeEndFromLong(range); - } - } - - Selection.setSelection((Spannable) mText, selectionStart, selectionEnd); - return selectionEnd > selectionStart; - } - /** * This is a temporary method. Future versions may support multi-locale text. * @@ -7877,45 +7302,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * This method is used by the ArrowKeyMovementMethod to jump from one word to the other. + * Made available to achieve a consistent behavior. * @hide */ public WordIterator getWordIterator() { - if (getEditor().mWordIterator == null) { - getEditor().mWordIterator = new WordIterator(getTextServicesLocale()); - } - return getEditor().mWordIterator; - } - - private long getCharRange(int offset) { - final int textLength = mText.length(); - if (offset + 1 < textLength) { - final char currentChar = mText.charAt(offset); - final char nextChar = mText.charAt(offset + 1); - if (Character.isSurrogatePair(currentChar, nextChar)) { - return TextUtils.packRangeInLong(offset, offset + 2); - } - } - if (offset < textLength) { - return TextUtils.packRangeInLong(offset, offset + 1); - } - if (offset - 2 >= 0) { - final char previousChar = mText.charAt(offset - 1); - final char previousPreviousChar = mText.charAt(offset - 2); - if (Character.isSurrogatePair(previousPreviousChar, previousChar)) { - return TextUtils.packRangeInLong(offset - 2, offset); - } - } - if (offset - 1 >= 0) { - return TextUtils.packRangeInLong(offset - 1, offset); + if (getEditor() != null) { + return mEditor.getWordIterator(); + } else { + return null; } - return TextUtils.packRangeInLong(offset, offset); - } - - private long getLastTouchOffsets() { - SelectionModifierCursorController selectionController = getSelectionController(); - final int minOffset = selectionController.getMinTouchOffset(); - final int maxOffset = selectionController.getMaxTouchOffset(); - return TextUtils.packRangeInLong(minOffset, maxOffset); } @Override @@ -8004,11 +7400,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return imm != null && imm.isActive(this); } - // Selection context mode - private static final int ID_SELECT_ALL = android.R.id.selectAll; - private static final int ID_CUT = android.R.id.cut; - private static final int ID_COPY = android.R.id.copy; - private static final int ID_PASTE = android.R.id.paste; + static final int ID_SELECT_ALL = android.R.id.selectAll; + static final int ID_CUT = android.R.id.cut; + static final int ID_COPY = android.R.id.copy; + static final int ID_PASTE = android.R.id.paste; /** * Called when a context menu option for the text view is selected. Currently @@ -8033,7 +7428,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case ID_SELECT_ALL: // This does not enter text selection mode. Text is highlighted, so that it can be // bulk edited, like selectAllOnFocus does. Returns true even if text is empty. - selectAll(); + selectAllText(); return true; case ID_PASTE: @@ -8054,89 +7449,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } - private CharSequence getTransformedText(int start, int end) { + CharSequence getTransformedText(int start, int end) { return removeSuggestionSpans(mTransformed.subSequence(start, end)); } - /** - * Prepare text so that there are not zero or two spaces at beginning and end of region defined - * by [min, max] when replacing this region by paste. - * Note that if there were two spaces (or more) at that position before, they are kept. We just - * make sure we do not add an extra one from the paste content. - */ - private long prepareSpacesAroundPaste(int min, int max, CharSequence paste) { - if (paste.length() > 0) { - if (min > 0) { - final char charBefore = mTransformed.charAt(min - 1); - final char charAfter = paste.charAt(0); - - if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) { - // Two spaces at beginning of paste: remove one - final int originalLength = mText.length(); - deleteText_internal(min - 1, min); - // Due to filters, there is no guarantee that exactly one character was - // removed: count instead. - final int delta = mText.length() - originalLength; - min += delta; - max += delta; - } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' && - !Character.isSpaceChar(charAfter) && charAfter != '\n') { - // No space at beginning of paste: add one - final int originalLength = mText.length(); - replaceText_internal(min, min, " "); - // Taking possible filters into account as above. - final int delta = mText.length() - originalLength; - min += delta; - max += delta; - } - } - - if (max < mText.length()) { - final char charBefore = paste.charAt(paste.length() - 1); - final char charAfter = mTransformed.charAt(max); - - if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) { - // Two spaces at end of paste: remove one - deleteText_internal(max, max + 1); - } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' && - !Character.isSpaceChar(charAfter) && charAfter != '\n') { - // No space at end of paste: add one - replaceText_internal(max, max, " "); - } - } - } - - return TextUtils.packRangeInLong(min, max); - } - - private DragShadowBuilder getTextThumbnailBuilder(CharSequence text) { - TextView shadowView = (TextView) inflate(mContext, - com.android.internal.R.layout.text_drag_thumbnail, null); - - if (shadowView == null) { - throw new IllegalArgumentException("Unable to inflate text drag thumbnail"); - } - - if (text.length() > DRAG_SHADOW_MAX_TEXT_LENGTH) { - text = text.subSequence(0, DRAG_SHADOW_MAX_TEXT_LENGTH); - } - shadowView.setText(text); - shadowView.setTextColor(getTextColors()); - - shadowView.setTextAppearance(mContext, R.styleable.Theme_textAppearanceLarge); - shadowView.setGravity(Gravity.CENTER); - - shadowView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - - final int size = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - shadowView.measure(size, size); - - shadowView.layout(0, 0, shadowView.getMeasuredWidth(), shadowView.getMeasuredHeight()); - shadowView.invalidate(); - return new DragShadowBuilder(shadowView); - } - @Override public boolean performLongClick() { boolean handled = false; @@ -8145,179 +7461,27 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener handled = true; } - if (mEditor == null) { - return handled; - } - - // Long press in empty space moves cursor and shows the Paste affordance if available. - if (!handled && !isPositionOnText(getEditor().mLastDownPositionX, getEditor().mLastDownPositionY) && - getEditor().mInsertionControllerEnabled) { - final int offset = getOffsetForPosition(getEditor().mLastDownPositionX, getEditor().mLastDownPositionY); - stopSelectionActionMode(); - Selection.setSelection((Spannable) mText, offset); - getInsertionController().showWithActionPopup(); - handled = true; - } - - if (!handled && getEditor().mSelectionActionMode != null) { - if (touchPositionIsInSelection()) { - // Start a drag - final int start = getSelectionStart(); - final int end = getSelectionEnd(); - CharSequence selectedText = getTransformedText(start, end); - ClipData data = ClipData.newPlainText(null, selectedText); - DragLocalState localState = new DragLocalState(this, start, end); - startDrag(data, getTextThumbnailBuilder(selectedText), localState, 0); - stopSelectionActionMode(); - } else { - getSelectionController().hide(); - selectCurrentWord(); - getSelectionController().show(); - } - handled = true; - } - - // Start a new selection - if (!handled) { - handled = startSelectionActionMode(); + if (mEditor != null) { + handled |= getEditor().performLongClick(handled); } if (handled) { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - getEditor().mDiscardNextActionUp = true; + if (mEditor != null) getEditor().mDiscardNextActionUp = true; } return handled; } - private boolean touchPositionIsInSelection() { - int selectionStart = getSelectionStart(); - int selectionEnd = getSelectionEnd(); - - if (selectionStart == selectionEnd) { - return false; - } - - if (selectionStart > selectionEnd) { - int tmp = selectionStart; - selectionStart = selectionEnd; - selectionEnd = tmp; - Selection.setSelection((Spannable) mText, selectionStart, selectionEnd); - } - - SelectionModifierCursorController selectionController = getSelectionController(); - int minOffset = selectionController.getMinTouchOffset(); - int maxOffset = selectionController.getMaxTouchOffset(); - - return ((minOffset >= selectionStart) && (maxOffset < selectionEnd)); - } - - private PositionListener getPositionListener() { - if (getEditor().mPositionListener == null) { - getEditor().mPositionListener = new PositionListener(); - } - return getEditor().mPositionListener; - } - - private interface TextViewPositionListener { - public void updatePosition(int parentPositionX, int parentPositionY, - boolean parentPositionChanged, boolean parentScrolled); - } - - private boolean isPositionVisible(int positionX, int positionY) { - synchronized (TEMP_POSITION) { - final float[] position = TEMP_POSITION; - position[0] = positionX; - position[1] = positionY; - View view = this; - - while (view != null) { - if (view != this) { - // Local scroll is already taken into account in positionX/Y - position[0] -= view.getScrollX(); - position[1] -= view.getScrollY(); - } - - if (position[0] < 0 || position[1] < 0 || - position[0] > view.getWidth() || position[1] > view.getHeight()) { - return false; - } - - if (!view.getMatrix().isIdentity()) { - view.getMatrix().mapPoints(position); - } - - position[0] += view.getLeft(); - position[1] += view.getTop(); - - final ViewParent parent = view.getParent(); - if (parent instanceof View) { - view = (View) parent; - } else { - // We've reached the ViewRoot, stop iterating - view = null; - } - } - } - - // We've been able to walk up the view hierarchy and the position was never clipped - return true; - } - - private boolean isOffsetVisible(int offset) { - final int line = mLayout.getLineForOffset(offset); - final int lineBottom = mLayout.getLineBottom(line); - final int primaryHorizontal = (int) mLayout.getPrimaryHorizontal(offset); - return isPositionVisible(primaryHorizontal + viewportToContentHorizontalOffset(), - lineBottom + viewportToContentVerticalOffset()); - } - @Override protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { super.onScrollChanged(horiz, vert, oldHoriz, oldVert); if (mEditor != null) { - if (getEditor().mPositionListener != null) { - getEditor().mPositionListener.onScrollChanged(); - } - // Internal scroll affects the clip boundaries - getEditor().invalidateTextDisplayList(); + getEditor().onScrollChanged(); } } /** - * Removes the suggestion spans. - */ - CharSequence removeSuggestionSpans(CharSequence text) { - if (text instanceof Spanned) { - Spannable spannable; - if (text instanceof Spannable) { - spannable = (Spannable) text; - } else { - spannable = new SpannableString(text); - text = spannable; - } - - SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); - for (int i = 0; i < spans.length; i++) { - spannable.removeSpan(spans[i]); - } - } - return text; - } - - void showSuggestions() { - if (getEditor().mSuggestionsPopupWindow == null) { - getEditor().mSuggestionsPopupWindow = new SuggestionsPopupWindow(); - } - hideControllers(); - getEditor().mSuggestionsPopupWindow.show(); - } - - boolean areSuggestionsShown() { - return getEditor().mSuggestionsPopupWindow != null && getEditor().mSuggestionsPopupWindow.isShowing(); - } - - /** * Return whether or not suggestions are enabled on this TextView. The suggestions are generated * by the IME or by the spell checker as the user types. This is done by adding * {@link SuggestionSpan}s to the text. @@ -8391,65 +7555,100 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** - * - * @return true if the selection mode was actually started. + * @hide */ - private boolean startSelectionActionMode() { - if (getEditor().mSelectionActionMode != null) { - // Selection action mode is already started - return false; - } + protected void stopSelectionActionMode() { + getEditor().stopSelectionActionMode(); + } - if (!canSelectText() || !requestFocus()) { - Log.w(LOG_TAG, "TextView does not support text selection. Action mode cancelled."); + boolean canCut() { + if (hasPasswordTransformationMethod()) { return false; } - if (!hasSelection()) { - // There may already be a selection on device rotation - if (!selectCurrentWord()) { - // No word found under cursor or text selection not permitted. - return false; - } + if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null && getEditor().mKeyListener != null) { + return true; } - boolean willExtract = extractedTextModeWillBeStarted(); + return false; + } - // Do not start the action mode when extracted text will show up full screen, which would - // immediately hide the newly created action bar and would be visually distracting. - if (!willExtract) { - ActionMode.Callback actionModeCallback = new SelectionActionModeCallback(); - getEditor().mSelectionActionMode = startActionMode(actionModeCallback); + boolean canCopy() { + if (hasPasswordTransformationMethod()) { + return false; } - final boolean selectionStarted = getEditor().mSelectionActionMode != null || willExtract; - if (selectionStarted && !isTextSelectable()) { - // Show the IME to be able to replace text, except when selecting non editable text. - final InputMethodManager imm = InputMethodManager.peekInstance(); - if (imm != null) { - imm.showSoftInput(this, 0, null); - } + if (mText.length() > 0 && hasSelection()) { + return true; } - return selectionStarted; + return false; } - private boolean extractedTextModeWillBeStarted() { - if (!(this instanceof ExtractEditText)) { - final InputMethodManager imm = InputMethodManager.peekInstance(); - return imm != null && imm.isFullscreenMode(); - } - return false; + boolean canPaste() { + return (mText instanceof Editable && + mEditor != null && getEditor().mKeyListener != null && + getSelectionStart() >= 0 && + getSelectionEnd() >= 0 && + ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)). + hasPrimaryClip()); + } + + boolean selectAllText() { + final int length = mText.length(); + Selection.setSelection((Spannable) mText, 0, length); + return length > 0; } /** - * @hide + * Prepare text so that there are not zero or two spaces at beginning and end of region defined + * by [min, max] when replacing this region by paste. + * Note that if there were two spaces (or more) at that position before, they are kept. We just + * make sure we do not add an extra one from the paste content. */ - protected void stopSelectionActionMode() { - if (getEditor().mSelectionActionMode != null) { - // This will hide the mSelectionModifierCursorController - getEditor().mSelectionActionMode.finish(); + long prepareSpacesAroundPaste(int min, int max, CharSequence paste) { + if (paste.length() > 0) { + if (min > 0) { + final char charBefore = mTransformed.charAt(min - 1); + final char charAfter = paste.charAt(0); + + if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) { + // Two spaces at beginning of paste: remove one + final int originalLength = mText.length(); + deleteText_internal(min - 1, min); + // Due to filters, there is no guarantee that exactly one character was + // removed: count instead. + final int delta = mText.length() - originalLength; + min += delta; + max += delta; + } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' && + !Character.isSpaceChar(charAfter) && charAfter != '\n') { + // No space at beginning of paste: add one + final int originalLength = mText.length(); + replaceText_internal(min, min, " "); + // Taking possible filters into account as above. + final int delta = mText.length() - originalLength; + min += delta; + max += delta; + } + } + + if (max < mText.length()) { + final char charBefore = paste.charAt(paste.length() - 1); + final char charAfter = mTransformed.charAt(max); + + if (Character.isSpaceChar(charBefore) && Character.isSpaceChar(charAfter)) { + // Two spaces at end of paste: remove one + deleteText_internal(max, max + 1); + } else if (!Character.isSpaceChar(charBefore) && charBefore != '\n' && + !Character.isSpaceChar(charAfter) && charAfter != '\n') { + // No space at end of paste: add one + replaceText_internal(max, max, " "); + } + } } + + return TextUtils.packRangeInLong(min, max); } /** @@ -8489,36 +7688,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis(); } - private void hideInsertionPointCursorController() { - // No need to create the controller to hide it. - if (getEditor().mInsertionPointCursorController != null) { - getEditor().mInsertionPointCursorController.hide(); - } - } - - /** - * Hides the insertion controller and stops text selection mode, hiding the selection controller - */ - private void hideControllers() { - hideCursorControllers(); - hideSpanControllers(); - } - - private void hideSpanControllers() { - if (mChangeWatcher != null) { - mChangeWatcher.hideControllers(); - } - } - - private void hideCursorControllers() { - if (getEditor().mSuggestionsPopupWindow != null && !getEditor().mSuggestionsPopupWindow.isShowingUp()) { - // Should be done before hide insertion point controller since it triggers a show of it - getEditor().mSuggestionsPopupWindow.hide(); - } - hideInsertionPointCursorController(); - stopSelectionActionMode(); - } - /** * Get the character offset closest to the specified absolute position. A typical use case is to * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method. @@ -8535,7 +7704,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return offset; } - private float convertToLocalHorizontalCoordinate(float x) { + float convertToLocalHorizontalCoordinate(float x) { x -= getTotalPaddingLeft(); // Clamp the position to inside of the view. x = Math.max(0.0f, x); @@ -8544,7 +7713,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return x; } - private int getLineAtCoordinate(float y) { + int getLineAtCoordinate(float y) { y -= getTotalPaddingTop(); // Clamp the position to inside of the view. y = Math.max(0.0f, y); @@ -8558,25 +7727,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return getLayout().getOffsetForHorizontal(line, x); } - /** Returns true if the screen coordinates position (x,y) corresponds to a character displayed - * in the view. Returns false when the position is in the empty space of left/right of text. - */ - private boolean isPositionOnText(float x, float y) { - if (getLayout() == null) return false; - - final int line = getLineAtCoordinate(y); - x = convertToLocalHorizontalCoordinate(x); - - if (x < getLayout().getLineLeft(line)) return false; - if (x > getLayout().getLineRight(line)) return false; - return true; - } - @Override public boolean onDragEvent(DragEvent event) { switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: - return mEditor != null && hasInsertionController(); + return mEditor != null && getEditor().hasInsertionController(); case DragEvent.ACTION_DRAG_ENTERED: TextView.this.requestFocus(); @@ -8588,7 +7743,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return true; case DragEvent.ACTION_DROP: - onDrop(event); + if (mEditor != null) getEditor().onDrop(event); return true; case DragEvent.ACTION_DRAG_ENDED: @@ -8598,112 +7753,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - private void onDrop(DragEvent event) { - StringBuilder content = new StringBuilder(""); - ClipData clipData = event.getClipData(); - final int itemCount = clipData.getItemCount(); - for (int i=0; i < itemCount; i++) { - Item item = clipData.getItemAt(i); - content.append(item.coerceToText(TextView.this.mContext)); - } - - final int offset = getOffsetForPosition(event.getX(), event.getY()); - - Object localState = event.getLocalState(); - DragLocalState dragLocalState = null; - if (localState instanceof DragLocalState) { - dragLocalState = (DragLocalState) localState; - } - boolean dragDropIntoItself = dragLocalState != null && - dragLocalState.sourceTextView == this; - - if (dragDropIntoItself) { - if (offset >= dragLocalState.start && offset < dragLocalState.end) { - // A drop inside the original selection discards the drop. - return; - } - } - - final int originalLength = mText.length(); - long minMax = prepareSpacesAroundPaste(offset, offset, content); - int min = TextUtils.unpackRangeStartFromLong(minMax); - int max = TextUtils.unpackRangeEndFromLong(minMax); - - Selection.setSelection((Spannable) mText, max); - replaceText_internal(min, max, content); - - if (dragDropIntoItself) { - int dragSourceStart = dragLocalState.start; - int dragSourceEnd = dragLocalState.end; - if (max <= dragSourceStart) { - // Inserting text before selection has shifted positions - final int shift = mText.length() - originalLength; - dragSourceStart += shift; - dragSourceEnd += shift; - } - - // Delete original selection - deleteText_internal(dragSourceStart, dragSourceEnd); - - // Make sure we do not leave two adjacent spaces. - if ((dragSourceStart == 0 || - Character.isSpaceChar(mTransformed.charAt(dragSourceStart - 1))) && - (dragSourceStart == mText.length() || - Character.isSpaceChar(mTransformed.charAt(dragSourceStart)))) { - final int pos = dragSourceStart == mText.length() ? - dragSourceStart - 1 : dragSourceStart; - deleteText_internal(pos, pos + 1); - } - } - } - - /** - * @return True if this view supports insertion handles. - */ - boolean hasInsertionController() { - return getEditor().mInsertionControllerEnabled; - } - - /** - * @return True if this view supports selection handles. - */ - boolean hasSelectionController() { - return getEditor().mSelectionControllerEnabled; - } - - InsertionPointCursorController getInsertionController() { - if (!getEditor().mInsertionControllerEnabled) { - return null; - } - - if (getEditor().mInsertionPointCursorController == null) { - getEditor().mInsertionPointCursorController = new InsertionPointCursorController(); - - final ViewTreeObserver observer = getViewTreeObserver(); - observer.addOnTouchModeChangeListener(getEditor().mInsertionPointCursorController); - } - - return getEditor().mInsertionPointCursorController; - } - - SelectionModifierCursorController getSelectionController() { - if (!getEditor().mSelectionControllerEnabled) { - return null; - } - - if (getEditor().mSelectionModifierCursorController == null) { - getEditor().mSelectionModifierCursorController = new SelectionModifierCursorController(); - - final ViewTreeObserver observer = getViewTreeObserver(); - observer.addOnTouchModeChangeListener(getEditor().mSelectionModifierCursorController); - } - - return getEditor().mSelectionModifierCursorController; - } - boolean isInBatchEditMode() { if (mEditor == null) return false; - final InputMethodState ims = getEditor().mInputMethodState; + final Editor.InputMethodState ims = getEditor().mInputMethodState; if (ims != null) { return ims.mBatchEditNesting > 0; } @@ -8864,7 +7916,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (!(this instanceof EditText)) { Log.e(LOG_TAG + " EDITOR", "Creating Editor on TextView. " + reason); } - mEditor = new Editor(); + mEditor = new Editor(this); } else { if (!(this instanceof EditText)) { Log.d(LOG_TAG + " EDITOR", "Redundant Editor creation. " + reason); @@ -9041,151 +8093,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - private static class ErrorPopup extends PopupWindow { - private boolean mAbove = false; - private final TextView mView; - private int mPopupInlineErrorBackgroundId = 0; - private int mPopupInlineErrorAboveBackgroundId = 0; - - ErrorPopup(TextView v, int width, int height) { - super(v, width, height); - mView = v; - // Make sure the TextView has a background set as it will be used the first time it is - // shown and positionned. Initialized with below background, which should have - // dimensions identical to the above version for this to work (and is more likely). - mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId, - com.android.internal.R.styleable.Theme_errorMessageBackground); - mView.setBackgroundResource(mPopupInlineErrorBackgroundId); - } - - void fixDirection(boolean above) { - mAbove = above; - - if (above) { - mPopupInlineErrorAboveBackgroundId = - getResourceId(mPopupInlineErrorAboveBackgroundId, - com.android.internal.R.styleable.Theme_errorMessageAboveBackground); - } else { - mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId, - com.android.internal.R.styleable.Theme_errorMessageBackground); - } - - mView.setBackgroundResource(above ? mPopupInlineErrorAboveBackgroundId : - mPopupInlineErrorBackgroundId); - } - - private int getResourceId(int currentId, int index) { - if (currentId == 0) { - TypedArray styledAttributes = mView.getContext().obtainStyledAttributes( - R.styleable.Theme); - currentId = styledAttributes.getResourceId(index, 0); - styledAttributes.recycle(); - } - return currentId; - } - - @Override - public void update(int x, int y, int w, int h, boolean force) { - super.update(x, y, w, h, force); - - boolean above = isAboveAnchor(); - if (above != mAbove) { - fixDirection(above); - } - } - } - - private class CorrectionHighlighter { - private final Path mPath = new Path(); - private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private int mStart, mEnd; - private long mFadingStartTime; - private final static int FADE_OUT_DURATION = 400; - - public CorrectionHighlighter() { - mPaint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale); - mPaint.setStyle(Paint.Style.FILL); - } - - public void highlight(CorrectionInfo info) { - mStart = info.getOffset(); - mEnd = mStart + info.getNewText().length(); - mFadingStartTime = SystemClock.uptimeMillis(); - - if (mStart < 0 || mEnd < 0) { - stopAnimation(); - } - } - - public void draw(Canvas canvas, int cursorOffsetVertical) { - if (updatePath() && updatePaint()) { - if (cursorOffsetVertical != 0) { - canvas.translate(0, cursorOffsetVertical); - } - - canvas.drawPath(mPath, mPaint); - - if (cursorOffsetVertical != 0) { - canvas.translate(0, -cursorOffsetVertical); - } - invalidate(true); // TODO invalidate cursor region only - } else { - stopAnimation(); - invalidate(false); // TODO invalidate cursor region only - } - } - - private boolean updatePaint() { - final long duration = SystemClock.uptimeMillis() - mFadingStartTime; - if (duration > FADE_OUT_DURATION) return false; - - final float coef = 1.0f - (float) duration / FADE_OUT_DURATION; - final int highlightColorAlpha = Color.alpha(mHighlightColor); - final int color = (mHighlightColor & 0x00FFFFFF) + - ((int) (highlightColorAlpha * coef) << 24); - mPaint.setColor(color); - return true; - } - - private boolean updatePath() { - final Layout layout = TextView.this.mLayout; - if (layout == null) return false; - - // Update in case text is edited while the animation is run - final int length = mText.length(); - int start = Math.min(length, mStart); - int end = Math.min(length, mEnd); - - mPath.reset(); - TextView.this.mLayout.getSelectionPath(start, end, mPath); - return true; - } - - private void invalidate(boolean delayed) { - if (TextView.this.mLayout == null) return; - - synchronized (TEMP_RECTF) { - mPath.computeBounds(TEMP_RECTF, false); - - int left = getCompoundPaddingLeft(); - int top = getExtendedPaddingTop() + getVerticalOffset(true); - - if (delayed) { - TextView.this.postInvalidateOnAnimation( - left + (int) TEMP_RECTF.left, top + (int) TEMP_RECTF.top, - left + (int) TEMP_RECTF.right, top + (int) TEMP_RECTF.bottom); - } else { - TextView.this.postInvalidate((int) TEMP_RECTF.left, (int) TEMP_RECTF.top, - (int) TEMP_RECTF.right, (int) TEMP_RECTF.bottom); - } - } - } - - private void stopAnimation() { - TextView.this.getEditor().mCorrectionHighlighter = null; - } - } - private static final class Marquee extends Handler { // TODO: Add an option to configure this private static final float MARQUEE_DELTA_MAX = 0.07f; @@ -9322,217 +8229,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - /** - * Controls the {@link EasyEditSpan} monitoring when it is added, and when the related - * pop-up should be displayed. - */ - private class EasyEditSpanController { - - private static final int DISPLAY_TIMEOUT_MS = 3000; // 3 secs - - private EasyEditPopupWindow mPopupWindow; - - private EasyEditSpan mEasyEditSpan; - - private Runnable mHidePopup; - - private void hide() { - if (mPopupWindow != null) { - mPopupWindow.hide(); - TextView.this.removeCallbacks(mHidePopup); - } - removeSpans(mText); - mEasyEditSpan = null; - } - - /** - * Monitors the changes in the text. - * - * <p>{@link ChangeWatcher#onSpanAdded(Spannable, Object, int, int)} cannot be used, - * as the notifications are not sent when a spannable (with spans) is inserted. - */ - public void onTextChange(CharSequence buffer) { - adjustSpans(mText); - - if (getWindowVisibility() != View.VISIBLE) { - // The window is not visible yet, ignore the text change. - return; - } - - if (mLayout == null) { - // The view has not been layout yet, ignore the text change - return; - } - - InputMethodManager imm = InputMethodManager.peekInstance(); - if (!(TextView.this instanceof ExtractEditText) - && imm != null && imm.isFullscreenMode()) { - // The input is in extract mode. We do not have to handle the easy edit in the - // original TextView, as the ExtractEditText will do - return; - } - - // Remove the current easy edit span, as the text changed, and remove the pop-up - // (if any) - if (mEasyEditSpan != null) { - if (mText instanceof Spannable) { - ((Spannable) mText).removeSpan(mEasyEditSpan); - } - mEasyEditSpan = null; - } - if (mPopupWindow != null && mPopupWindow.isShowing()) { - mPopupWindow.hide(); - } - - // Display the new easy edit span (if any). - if (buffer instanceof Spanned) { - mEasyEditSpan = getSpan((Spanned) buffer); - if (mEasyEditSpan != null) { - if (mPopupWindow == null) { - mPopupWindow = new EasyEditPopupWindow(); - mHidePopup = new Runnable() { - @Override - public void run() { - hide(); - } - }; - } - mPopupWindow.show(mEasyEditSpan); - TextView.this.removeCallbacks(mHidePopup); - TextView.this.postDelayed(mHidePopup, DISPLAY_TIMEOUT_MS); - } - } - } - - /** - * Adjusts the spans by removing all of them except the last one. - */ - private void adjustSpans(CharSequence buffer) { - // This method enforces that only one easy edit span is attached to the text. - // A better way to enforce this would be to listen for onSpanAdded, but this method - // cannot be used in this scenario as no notification is triggered when a text with - // spans is inserted into a text. - if (buffer instanceof Spannable) { - Spannable spannable = (Spannable) buffer; - EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(), - EasyEditSpan.class); - for (int i = 0; i < spans.length - 1; i++) { - spannable.removeSpan(spans[i]); - } - } - } - - /** - * Removes all the {@link EasyEditSpan} currently attached. - */ - private void removeSpans(CharSequence buffer) { - if (buffer instanceof Spannable) { - Spannable spannable = (Spannable) buffer; - EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(), - EasyEditSpan.class); - for (int i = 0; i < spans.length; i++) { - spannable.removeSpan(spans[i]); - } - } - } - - private EasyEditSpan getSpan(Spanned spanned) { - EasyEditSpan[] easyEditSpans = spanned.getSpans(0, spanned.length(), - EasyEditSpan.class); - if (easyEditSpans.length == 0) { - return null; - } else { - return easyEditSpans[0]; - } - } - } - - /** - * Displays the actions associated to an {@link EasyEditSpan}. The pop-up is controlled - * by {@link EasyEditSpanController}. - */ - private class EasyEditPopupWindow extends PinnedPopupWindow - implements OnClickListener { - private static final int POPUP_TEXT_LAYOUT = - com.android.internal.R.layout.text_edit_action_popup_text; - private TextView mDeleteTextView; - private EasyEditSpan mEasyEditSpan; - - @Override - protected void createPopupWindow() { - mPopupWindow = new PopupWindow(TextView.this.mContext, null, - com.android.internal.R.attr.textSelectHandleWindowStyle); - mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); - mPopupWindow.setClippingEnabled(true); - } - - @Override - protected void initContentView() { - LinearLayout linearLayout = new LinearLayout(TextView.this.getContext()); - linearLayout.setOrientation(LinearLayout.HORIZONTAL); - mContentView = linearLayout; - mContentView.setBackgroundResource( - com.android.internal.R.drawable.text_edit_side_paste_window); - - LayoutInflater inflater = (LayoutInflater)TextView.this.mContext. - getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - LayoutParams wrapContent = new LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - mDeleteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null); - mDeleteTextView.setLayoutParams(wrapContent); - mDeleteTextView.setText(com.android.internal.R.string.delete); - mDeleteTextView.setOnClickListener(this); - mContentView.addView(mDeleteTextView); - } - - public void show(EasyEditSpan easyEditSpan) { - mEasyEditSpan = easyEditSpan; - super.show(); - } - - @Override - public void onClick(View view) { - if (view == mDeleteTextView) { - Editable editable = (Editable) mText; - int start = editable.getSpanStart(mEasyEditSpan); - int end = editable.getSpanEnd(mEasyEditSpan); - if (start >= 0 && end >= 0) { - deleteText_internal(start, end); - } - } - } - - @Override - protected int getTextOffset() { - // Place the pop-up at the end of the span - Editable editable = (Editable) mText; - return editable.getSpanEnd(mEasyEditSpan); - } - - @Override - protected int getVerticalLocalPosition(int line) { - return mLayout.getLineBottom(line); - } - - @Override - protected int clipVertically(int positionY) { - // As we display the pop-up below the span, no vertical clipping is required. - return positionY; - } - } - private class ChangeWatcher implements TextWatcher, SpanWatcher { private CharSequence mBeforeText; - private EasyEditSpanController mEasyEditSpanController; - - private ChangeWatcher() { - mEasyEditSpanController = new EasyEditSpanController(); - } - public void beforeTextChanged(CharSequence buffer, int start, int before, int after) { if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start @@ -9547,14 +8247,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener TextView.this.sendBeforeTextChanged(buffer, start, before, after); } - public void onTextChanged(CharSequence buffer, int start, - int before, int after) { + public void onTextChanged(CharSequence buffer, int start, int before, int after) { if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start + " before=" + before + " after=" + after + ": " + buffer); TextView.this.handleTextChanged(buffer, start, before, after); - mEasyEditSpanController.onTextChange(buffer); - if (AccessibilityManager.getInstance(mContext).isEnabled() && (isFocused() || isSelected() && isShown())) { sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); @@ -9571,8 +8268,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - public void onSpanChanged(Spannable buf, - Object what, int s, int e, int st, int en) { + public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e + " st=" + st + " en=" + en + " what=" + what + ": " + buf); TextView.this.spanChange(buf, what, s, st, e, en); @@ -9589,2264 +8285,5 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener + " what=" + what + ": " + buf); TextView.this.spanChange(buf, what, s, -1, e, -1); } - - private void hideControllers() { - mEasyEditSpanController.hide(); - } - } - - private static class Blink extends Handler implements Runnable { - private final WeakReference<TextView> mView; - private boolean mCancelled; - - public Blink(TextView v) { - mView = new WeakReference<TextView>(v); - } - - public void run() { - if (mCancelled) { - return; - } - - removeCallbacks(Blink.this); - - TextView tv = mView.get(); - - if (tv != null && tv.shouldBlink()) { - if (tv.mLayout != null) { - tv.invalidateCursorPath(); - } - - postAtTime(this, SystemClock.uptimeMillis() + BLINK); - } - } - - void cancel() { - if (!mCancelled) { - removeCallbacks(Blink.this); - mCancelled = true; - } - } - - void uncancel() { - mCancelled = false; - } - } - - private static class DragLocalState { - public TextView sourceTextView; - public int start, end; - - public DragLocalState(TextView sourceTextView, int start, int end) { - this.sourceTextView = sourceTextView; - this.start = start; - this.end = end; - } - } - - private class PositionListener implements ViewTreeObserver.OnPreDrawListener { - // 3 handles - // 3 ActionPopup [replace, suggestion, easyedit] (suggestionsPopup first hides the others) - private final int MAXIMUM_NUMBER_OF_LISTENERS = 6; - private TextViewPositionListener[] mPositionListeners = - new TextViewPositionListener[MAXIMUM_NUMBER_OF_LISTENERS]; - private boolean mCanMove[] = new boolean[MAXIMUM_NUMBER_OF_LISTENERS]; - private boolean mPositionHasChanged = true; - // Absolute position of the TextView with respect to its parent window - private int mPositionX, mPositionY; - private int mNumberOfListeners; - private boolean mScrollHasChanged; - final int[] mTempCoords = new int[2]; - - public void addSubscriber(TextViewPositionListener positionListener, boolean canMove) { - if (mNumberOfListeners == 0) { - updatePosition(); - ViewTreeObserver vto = TextView.this.getViewTreeObserver(); - vto.addOnPreDrawListener(this); - } - - int emptySlotIndex = -1; - for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) { - TextViewPositionListener listener = mPositionListeners[i]; - if (listener == positionListener) { - return; - } else if (emptySlotIndex < 0 && listener == null) { - emptySlotIndex = i; - } - } - - mPositionListeners[emptySlotIndex] = positionListener; - mCanMove[emptySlotIndex] = canMove; - mNumberOfListeners++; - } - - public void removeSubscriber(TextViewPositionListener positionListener) { - for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) { - if (mPositionListeners[i] == positionListener) { - mPositionListeners[i] = null; - mNumberOfListeners--; - break; - } - } - - if (mNumberOfListeners == 0) { - ViewTreeObserver vto = TextView.this.getViewTreeObserver(); - vto.removeOnPreDrawListener(this); - } - } - - public int getPositionX() { - return mPositionX; - } - - public int getPositionY() { - return mPositionY; - } - - @Override - public boolean onPreDraw() { - updatePosition(); - - for (int i = 0; i < MAXIMUM_NUMBER_OF_LISTENERS; i++) { - if (mPositionHasChanged || mScrollHasChanged || mCanMove[i]) { - TextViewPositionListener positionListener = mPositionListeners[i]; - if (positionListener != null) { - positionListener.updatePosition(mPositionX, mPositionY, - mPositionHasChanged, mScrollHasChanged); - } - } - } - - mScrollHasChanged = false; - return true; - } - - private void updatePosition() { - TextView.this.getLocationInWindow(mTempCoords); - - mPositionHasChanged = mTempCoords[0] != mPositionX || mTempCoords[1] != mPositionY; - - mPositionX = mTempCoords[0]; - mPositionY = mTempCoords[1]; - } - - public void onScrollChanged() { - mScrollHasChanged = true; - } - } - - private abstract class PinnedPopupWindow implements TextViewPositionListener { - protected PopupWindow mPopupWindow; - protected ViewGroup mContentView; - int mPositionX, mPositionY; - - protected abstract void createPopupWindow(); - protected abstract void initContentView(); - protected abstract int getTextOffset(); - protected abstract int getVerticalLocalPosition(int line); - protected abstract int clipVertically(int positionY); - - public PinnedPopupWindow() { - createPopupWindow(); - - mPopupWindow.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL); - mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT); - mPopupWindow.setHeight(ViewGroup.LayoutParams.WRAP_CONTENT); - - initContentView(); - - LayoutParams wrapContent = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT); - mContentView.setLayoutParams(wrapContent); - - mPopupWindow.setContentView(mContentView); - } - - public void show() { - TextView.this.getPositionListener().addSubscriber(this, false /* offset is fixed */); - - computeLocalPosition(); - - final PositionListener positionListener = TextView.this.getPositionListener(); - updatePosition(positionListener.getPositionX(), positionListener.getPositionY()); - } - - protected void measureContent() { - final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); - mContentView.measure( - View.MeasureSpec.makeMeasureSpec(displayMetrics.widthPixels, - View.MeasureSpec.AT_MOST), - View.MeasureSpec.makeMeasureSpec(displayMetrics.heightPixels, - View.MeasureSpec.AT_MOST)); - } - - /* The popup window will be horizontally centered on the getTextOffset() and vertically - * positioned according to viewportToContentHorizontalOffset. - * - * This method assumes that mContentView has properly been measured from its content. */ - private void computeLocalPosition() { - measureContent(); - final int width = mContentView.getMeasuredWidth(); - final int offset = getTextOffset(); - mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - width / 2.0f); - mPositionX += viewportToContentHorizontalOffset(); - - final int line = mLayout.getLineForOffset(offset); - mPositionY = getVerticalLocalPosition(line); - mPositionY += viewportToContentVerticalOffset(); - } - - private void updatePosition(int parentPositionX, int parentPositionY) { - int positionX = parentPositionX + mPositionX; - int positionY = parentPositionY + mPositionY; - - positionY = clipVertically(positionY); - - // Horizontal clipping - final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); - final int width = mContentView.getMeasuredWidth(); - positionX = Math.min(displayMetrics.widthPixels - width, positionX); - positionX = Math.max(0, positionX); - - if (isShowing()) { - mPopupWindow.update(positionX, positionY, -1, -1); - } else { - mPopupWindow.showAtLocation(TextView.this, Gravity.NO_GRAVITY, - positionX, positionY); - } - } - - public void hide() { - mPopupWindow.dismiss(); - TextView.this.getPositionListener().removeSubscriber(this); - } - - @Override - public void updatePosition(int parentPositionX, int parentPositionY, - boolean parentPositionChanged, boolean parentScrolled) { - // Either parentPositionChanged or parentScrolled is true, check if still visible - if (isShowing() && isOffsetVisible(getTextOffset())) { - if (parentScrolled) computeLocalPosition(); - updatePosition(parentPositionX, parentPositionY); - } else { - hide(); - } - } - - public boolean isShowing() { - return mPopupWindow.isShowing(); - } - } - - private class SuggestionsPopupWindow extends PinnedPopupWindow implements OnItemClickListener { - private static final int MAX_NUMBER_SUGGESTIONS = SuggestionSpan.SUGGESTIONS_MAX_SIZE; - private static final int ADD_TO_DICTIONARY = -1; - private static final int DELETE_TEXT = -2; - private SuggestionInfo[] mSuggestionInfos; - private int mNumberOfSuggestions; - private boolean mCursorWasVisibleBeforeSuggestions; - private boolean mIsShowingUp = false; - private SuggestionAdapter mSuggestionsAdapter; - private final Comparator<SuggestionSpan> mSuggestionSpanComparator; - private final HashMap<SuggestionSpan, Integer> mSpansLengths; - - private class CustomPopupWindow extends PopupWindow { - public CustomPopupWindow(Context context, int defStyle) { - super(context, null, defStyle); - } - - @Override - public void dismiss() { - super.dismiss(); - - TextView.this.getPositionListener().removeSubscriber(SuggestionsPopupWindow.this); - - // Safe cast since show() checks that mText is an Editable - ((Spannable) mText).removeSpan(getEditor().mSuggestionRangeSpan); - - setCursorVisible(mCursorWasVisibleBeforeSuggestions); - if (hasInsertionController()) { - getInsertionController().show(); - } - } - } - - public SuggestionsPopupWindow() { - mCursorWasVisibleBeforeSuggestions = getEditor().mCursorVisible; - mSuggestionSpanComparator = new SuggestionSpanComparator(); - mSpansLengths = new HashMap<SuggestionSpan, Integer>(); - } - - @Override - protected void createPopupWindow() { - mPopupWindow = new CustomPopupWindow(TextView.this.mContext, - com.android.internal.R.attr.textSuggestionsWindowStyle); - mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); - mPopupWindow.setFocusable(true); - mPopupWindow.setClippingEnabled(false); - } - - @Override - protected void initContentView() { - ListView listView = new ListView(TextView.this.getContext()); - mSuggestionsAdapter = new SuggestionAdapter(); - listView.setAdapter(mSuggestionsAdapter); - listView.setOnItemClickListener(this); - mContentView = listView; - - // Inflate the suggestion items once and for all. + 2 for add to dictionary and delete - mSuggestionInfos = new SuggestionInfo[MAX_NUMBER_SUGGESTIONS + 2]; - for (int i = 0; i < mSuggestionInfos.length; i++) { - mSuggestionInfos[i] = new SuggestionInfo(); - } - } - - public boolean isShowingUp() { - return mIsShowingUp; - } - - public void onParentLostFocus() { - mIsShowingUp = false; - } - - private class SuggestionInfo { - int suggestionStart, suggestionEnd; // range of actual suggestion within text - SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents - int suggestionIndex; // the index of this suggestion inside suggestionSpan - SpannableStringBuilder text = new SpannableStringBuilder(); - TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext, - android.R.style.TextAppearance_SuggestionHighlight); - } - - private class SuggestionAdapter extends BaseAdapter { - private LayoutInflater mInflater = (LayoutInflater) TextView.this.mContext. - getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - @Override - public int getCount() { - return mNumberOfSuggestions; - } - - @Override - public Object getItem(int position) { - return mSuggestionInfos[position]; - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - TextView textView = (TextView) convertView; - - if (textView == null) { - textView = (TextView) mInflater.inflate(mTextEditSuggestionItemLayout, parent, - false); - } - - final SuggestionInfo suggestionInfo = mSuggestionInfos[position]; - textView.setText(suggestionInfo.text); - - if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY) { - textView.setCompoundDrawablesWithIntrinsicBounds( - com.android.internal.R.drawable.ic_suggestions_add, 0, 0, 0); - } else if (suggestionInfo.suggestionIndex == DELETE_TEXT) { - textView.setCompoundDrawablesWithIntrinsicBounds( - com.android.internal.R.drawable.ic_suggestions_delete, 0, 0, 0); - } else { - textView.setCompoundDrawables(null, null, null, null); - } - - return textView; - } - } - - private class SuggestionSpanComparator implements Comparator<SuggestionSpan> { - public int compare(SuggestionSpan span1, SuggestionSpan span2) { - final int flag1 = span1.getFlags(); - final int flag2 = span2.getFlags(); - if (flag1 != flag2) { - // The order here should match what is used in updateDrawState - final boolean easy1 = (flag1 & SuggestionSpan.FLAG_EASY_CORRECT) != 0; - final boolean easy2 = (flag2 & SuggestionSpan.FLAG_EASY_CORRECT) != 0; - final boolean misspelled1 = (flag1 & SuggestionSpan.FLAG_MISSPELLED) != 0; - final boolean misspelled2 = (flag2 & SuggestionSpan.FLAG_MISSPELLED) != 0; - if (easy1 && !misspelled1) return -1; - if (easy2 && !misspelled2) return 1; - if (misspelled1) return -1; - if (misspelled2) return 1; - } - - return mSpansLengths.get(span1).intValue() - mSpansLengths.get(span2).intValue(); - } - } - - /** - * Returns the suggestion spans that cover the current cursor position. The suggestion - * spans are sorted according to the length of text that they are attached to. - */ - private SuggestionSpan[] getSuggestionSpans() { - int pos = TextView.this.getSelectionStart(); - Spannable spannable = (Spannable) TextView.this.mText; - SuggestionSpan[] suggestionSpans = spannable.getSpans(pos, pos, SuggestionSpan.class); - - mSpansLengths.clear(); - for (SuggestionSpan suggestionSpan : suggestionSpans) { - int start = spannable.getSpanStart(suggestionSpan); - int end = spannable.getSpanEnd(suggestionSpan); - mSpansLengths.put(suggestionSpan, Integer.valueOf(end - start)); - } - - // The suggestions are sorted according to their types (easy correction first, then - // misspelled) and to the length of the text that they cover (shorter first). - Arrays.sort(suggestionSpans, mSuggestionSpanComparator); - return suggestionSpans; - } - - @Override - public void show() { - if (!(mText instanceof Editable)) return; - - if (updateSuggestions()) { - mCursorWasVisibleBeforeSuggestions = getEditor().mCursorVisible; - setCursorVisible(false); - mIsShowingUp = true; - super.show(); - } - } - - @Override - protected void measureContent() { - final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); - final int horizontalMeasure = View.MeasureSpec.makeMeasureSpec( - displayMetrics.widthPixels, View.MeasureSpec.AT_MOST); - final int verticalMeasure = View.MeasureSpec.makeMeasureSpec( - displayMetrics.heightPixels, View.MeasureSpec.AT_MOST); - - int width = 0; - View view = null; - for (int i = 0; i < mNumberOfSuggestions; i++) { - view = mSuggestionsAdapter.getView(i, view, mContentView); - view.getLayoutParams().width = LayoutParams.WRAP_CONTENT; - view.measure(horizontalMeasure, verticalMeasure); - width = Math.max(width, view.getMeasuredWidth()); - } - - // Enforce the width based on actual text widths - mContentView.measure( - View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), - verticalMeasure); - - Drawable popupBackground = mPopupWindow.getBackground(); - if (popupBackground != null) { - if (mTempRect == null) mTempRect = new Rect(); - popupBackground.getPadding(mTempRect); - width += mTempRect.left + mTempRect.right; - } - mPopupWindow.setWidth(width); - } - - @Override - protected int getTextOffset() { - return getSelectionStart(); - } - - @Override - protected int getVerticalLocalPosition(int line) { - return mLayout.getLineBottom(line); - } - - @Override - protected int clipVertically(int positionY) { - final int height = mContentView.getMeasuredHeight(); - final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); - return Math.min(positionY, displayMetrics.heightPixels - height); - } - - @Override - public void hide() { - super.hide(); - } - - private boolean updateSuggestions() { - Spannable spannable = (Spannable) TextView.this.mText; - SuggestionSpan[] suggestionSpans = getSuggestionSpans(); - - final int nbSpans = suggestionSpans.length; - // Suggestions are shown after a delay: the underlying spans may have been removed - if (nbSpans == 0) return false; - - mNumberOfSuggestions = 0; - int spanUnionStart = mText.length(); - int spanUnionEnd = 0; - - SuggestionSpan misspelledSpan = null; - int underlineColor = 0; - - for (int spanIndex = 0; spanIndex < nbSpans; spanIndex++) { - SuggestionSpan suggestionSpan = suggestionSpans[spanIndex]; - final int spanStart = spannable.getSpanStart(suggestionSpan); - final int spanEnd = spannable.getSpanEnd(suggestionSpan); - spanUnionStart = Math.min(spanStart, spanUnionStart); - spanUnionEnd = Math.max(spanEnd, spanUnionEnd); - - if ((suggestionSpan.getFlags() & SuggestionSpan.FLAG_MISSPELLED) != 0) { - misspelledSpan = suggestionSpan; - } - - // The first span dictates the background color of the highlighted text - if (spanIndex == 0) underlineColor = suggestionSpan.getUnderlineColor(); - - String[] suggestions = suggestionSpan.getSuggestions(); - int nbSuggestions = suggestions.length; - for (int suggestionIndex = 0; suggestionIndex < nbSuggestions; suggestionIndex++) { - String suggestion = suggestions[suggestionIndex]; - - boolean suggestionIsDuplicate = false; - for (int i = 0; i < mNumberOfSuggestions; i++) { - if (mSuggestionInfos[i].text.toString().equals(suggestion)) { - SuggestionSpan otherSuggestionSpan = mSuggestionInfos[i].suggestionSpan; - final int otherSpanStart = spannable.getSpanStart(otherSuggestionSpan); - final int otherSpanEnd = spannable.getSpanEnd(otherSuggestionSpan); - if (spanStart == otherSpanStart && spanEnd == otherSpanEnd) { - suggestionIsDuplicate = true; - break; - } - } - } - - if (!suggestionIsDuplicate) { - SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions]; - suggestionInfo.suggestionSpan = suggestionSpan; - suggestionInfo.suggestionIndex = suggestionIndex; - suggestionInfo.text.replace(0, suggestionInfo.text.length(), suggestion); - - mNumberOfSuggestions++; - - if (mNumberOfSuggestions == MAX_NUMBER_SUGGESTIONS) { - // Also end outer for loop - spanIndex = nbSpans; - break; - } - } - } - } - - for (int i = 0; i < mNumberOfSuggestions; i++) { - highlightTextDifferences(mSuggestionInfos[i], spanUnionStart, spanUnionEnd); - } - - // Add "Add to dictionary" item if there is a span with the misspelled flag - if (misspelledSpan != null) { - final int misspelledStart = spannable.getSpanStart(misspelledSpan); - final int misspelledEnd = spannable.getSpanEnd(misspelledSpan); - if (misspelledStart >= 0 && misspelledEnd > misspelledStart) { - SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions]; - suggestionInfo.suggestionSpan = misspelledSpan; - suggestionInfo.suggestionIndex = ADD_TO_DICTIONARY; - suggestionInfo.text.replace(0, suggestionInfo.text.length(), - getContext().getString(com.android.internal.R.string.addToDictionary)); - suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, 0, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - - mNumberOfSuggestions++; - } - } - - // Delete item - SuggestionInfo suggestionInfo = mSuggestionInfos[mNumberOfSuggestions]; - suggestionInfo.suggestionSpan = null; - suggestionInfo.suggestionIndex = DELETE_TEXT; - suggestionInfo.text.replace(0, suggestionInfo.text.length(), - getContext().getString(com.android.internal.R.string.deleteText)); - suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, 0, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - mNumberOfSuggestions++; - - if (getEditor().mSuggestionRangeSpan == null) getEditor().mSuggestionRangeSpan = new SuggestionRangeSpan(); - if (underlineColor == 0) { - // Fallback on the default highlight color when the first span does not provide one - getEditor().mSuggestionRangeSpan.setBackgroundColor(mHighlightColor); - } else { - final float BACKGROUND_TRANSPARENCY = 0.4f; - final int newAlpha = (int) (Color.alpha(underlineColor) * BACKGROUND_TRANSPARENCY); - getEditor().mSuggestionRangeSpan.setBackgroundColor( - (underlineColor & 0x00FFFFFF) + (newAlpha << 24)); - } - spannable.setSpan(getEditor().mSuggestionRangeSpan, spanUnionStart, spanUnionEnd, - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - - mSuggestionsAdapter.notifyDataSetChanged(); - return true; - } - - private void highlightTextDifferences(SuggestionInfo suggestionInfo, int unionStart, - int unionEnd) { - final Spannable text = (Spannable) mText; - final int spanStart = text.getSpanStart(suggestionInfo.suggestionSpan); - final int spanEnd = text.getSpanEnd(suggestionInfo.suggestionSpan); - - // Adjust the start/end of the suggestion span - suggestionInfo.suggestionStart = spanStart - unionStart; - suggestionInfo.suggestionEnd = suggestionInfo.suggestionStart - + suggestionInfo.text.length(); - - suggestionInfo.text.setSpan(suggestionInfo.highlightSpan, 0, - suggestionInfo.text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - - // Add the text before and after the span. - final String textAsString = text.toString(); - suggestionInfo.text.insert(0, textAsString.substring(unionStart, spanStart)); - suggestionInfo.text.append(textAsString.substring(spanEnd, unionEnd)); - } - - @Override - public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - Editable editable = (Editable) mText; - SuggestionInfo suggestionInfo = mSuggestionInfos[position]; - - if (suggestionInfo.suggestionIndex == DELETE_TEXT) { - final int spanUnionStart = editable.getSpanStart(getEditor().mSuggestionRangeSpan); - int spanUnionEnd = editable.getSpanEnd(getEditor().mSuggestionRangeSpan); - if (spanUnionStart >= 0 && spanUnionEnd > spanUnionStart) { - // Do not leave two adjacent spaces after deletion, or one at beginning of text - if (spanUnionEnd < editable.length() && - Character.isSpaceChar(editable.charAt(spanUnionEnd)) && - (spanUnionStart == 0 || - Character.isSpaceChar(editable.charAt(spanUnionStart - 1)))) { - spanUnionEnd = spanUnionEnd + 1; - } - deleteText_internal(spanUnionStart, spanUnionEnd); - } - hide(); - return; - } - - final int spanStart = editable.getSpanStart(suggestionInfo.suggestionSpan); - final int spanEnd = editable.getSpanEnd(suggestionInfo.suggestionSpan); - if (spanStart < 0 || spanEnd <= spanStart) { - // Span has been removed - hide(); - return; - } - final String originalText = mText.toString().substring(spanStart, spanEnd); - - if (suggestionInfo.suggestionIndex == ADD_TO_DICTIONARY) { - Intent intent = new Intent(Settings.ACTION_USER_DICTIONARY_INSERT); - intent.putExtra("word", originalText); - intent.putExtra("locale", getTextServicesLocale().toString()); - intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); - getContext().startActivity(intent); - // There is no way to know if the word was indeed added. Re-check. - // TODO The ExtractEditText should remove the span in the original text instead - editable.removeSpan(suggestionInfo.suggestionSpan); - updateSpellCheckSpans(spanStart, spanEnd, false); - } else { - // SuggestionSpans are removed by replace: save them before - SuggestionSpan[] suggestionSpans = editable.getSpans(spanStart, spanEnd, - SuggestionSpan.class); - final int length = suggestionSpans.length; - int[] suggestionSpansStarts = new int[length]; - int[] suggestionSpansEnds = new int[length]; - int[] suggestionSpansFlags = new int[length]; - for (int i = 0; i < length; i++) { - final SuggestionSpan suggestionSpan = suggestionSpans[i]; - suggestionSpansStarts[i] = editable.getSpanStart(suggestionSpan); - suggestionSpansEnds[i] = editable.getSpanEnd(suggestionSpan); - suggestionSpansFlags[i] = editable.getSpanFlags(suggestionSpan); - - // Remove potential misspelled flags - int suggestionSpanFlags = suggestionSpan.getFlags(); - if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) > 0) { - suggestionSpanFlags &= ~SuggestionSpan.FLAG_MISSPELLED; - suggestionSpanFlags &= ~SuggestionSpan.FLAG_EASY_CORRECT; - suggestionSpan.setFlags(suggestionSpanFlags); - } - } - - final int suggestionStart = suggestionInfo.suggestionStart; - final int suggestionEnd = suggestionInfo.suggestionEnd; - final String suggestion = suggestionInfo.text.subSequence( - suggestionStart, suggestionEnd).toString(); - replaceText_internal(spanStart, spanEnd, suggestion); - - // Notify source IME of the suggestion pick. Do this before swaping texts. - if (!TextUtils.isEmpty( - suggestionInfo.suggestionSpan.getNotificationTargetClassName())) { - InputMethodManager imm = InputMethodManager.peekInstance(); - if (imm != null) { - imm.notifySuggestionPicked(suggestionInfo.suggestionSpan, originalText, - suggestionInfo.suggestionIndex); - } - } - - // Swap text content between actual text and Suggestion span - String[] suggestions = suggestionInfo.suggestionSpan.getSuggestions(); - suggestions[suggestionInfo.suggestionIndex] = originalText; - - // Restore previous SuggestionSpans - final int lengthDifference = suggestion.length() - (spanEnd - spanStart); - for (int i = 0; i < length; i++) { - // Only spans that include the modified region make sense after replacement - // Spans partially included in the replaced region are removed, there is no - // way to assign them a valid range after replacement - if (suggestionSpansStarts[i] <= spanStart && - suggestionSpansEnds[i] >= spanEnd) { - setSpan_internal(suggestionSpans[i], suggestionSpansStarts[i], - suggestionSpansEnds[i] + lengthDifference, suggestionSpansFlags[i]); - } - } - - // Move cursor at the end of the replaced word - final int newCursorPosition = spanEnd + lengthDifference; - setCursorPosition_internal(newCursorPosition, newCursorPosition); - } - - hide(); - } - } - - /** - * An ActionMode Callback class that is used to provide actions while in text selection mode. - * - * The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending - * on which of these this TextView supports. - */ - private class SelectionActionModeCallback implements ActionMode.Callback { - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - TypedArray styledAttributes = mContext.obtainStyledAttributes( - com.android.internal.R.styleable.SelectionModeDrawables); - - boolean allowText = getContext().getResources().getBoolean( - com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon); - - mode.setTitle(mContext.getString(com.android.internal.R.string.textSelectionCABTitle)); - mode.setSubtitle(null); - mode.setTitleOptionalHint(true); - - int selectAllIconId = 0; // No icon by default - if (!allowText) { - // Provide an icon, text will not be displayed on smaller screens. - selectAllIconId = styledAttributes.getResourceId( - R.styleable.SelectionModeDrawables_actionModeSelectAllDrawable, 0); - } - - menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll). - setIcon(selectAllIconId). - setAlphabeticShortcut('a'). - setShowAsAction( - MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - - if (canCut()) { - menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut). - setIcon(styledAttributes.getResourceId( - R.styleable.SelectionModeDrawables_actionModeCutDrawable, 0)). - setAlphabeticShortcut('x'). - setShowAsAction( - MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - } - - if (canCopy()) { - menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy). - setIcon(styledAttributes.getResourceId( - R.styleable.SelectionModeDrawables_actionModeCopyDrawable, 0)). - setAlphabeticShortcut('c'). - setShowAsAction( - MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - } - - if (canPaste()) { - menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste). - setIcon(styledAttributes.getResourceId( - R.styleable.SelectionModeDrawables_actionModePasteDrawable, 0)). - setAlphabeticShortcut('v'). - setShowAsAction( - MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - } - - styledAttributes.recycle(); - - if (getEditor().mCustomSelectionActionModeCallback != null) { - if (!getEditor().mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) { - // The custom mode can choose to cancel the action mode - return false; - } - } - - if (menu.hasVisibleItems() || mode.getCustomView() != null) { - getSelectionController().show(); - return true; - } else { - return false; - } - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - if (getEditor().mCustomSelectionActionModeCallback != null) { - return getEditor().mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu); - } - return true; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - if (getEditor().mCustomSelectionActionModeCallback != null && - getEditor().mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) { - return true; - } - return onTextContextMenuItem(item.getItemId()); - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - if (getEditor().mCustomSelectionActionModeCallback != null) { - getEditor().mCustomSelectionActionModeCallback.onDestroyActionMode(mode); - } - Selection.setSelection((Spannable) mText, getSelectionEnd()); - - if (getEditor().mSelectionModifierCursorController != null) { - getEditor().mSelectionModifierCursorController.hide(); - } - - getEditor().mSelectionActionMode = null; - } - } - - private class ActionPopupWindow extends PinnedPopupWindow implements OnClickListener { - private static final int POPUP_TEXT_LAYOUT = - com.android.internal.R.layout.text_edit_action_popup_text; - private TextView mPasteTextView; - private TextView mReplaceTextView; - - @Override - protected void createPopupWindow() { - mPopupWindow = new PopupWindow(TextView.this.mContext, null, - com.android.internal.R.attr.textSelectHandleWindowStyle); - mPopupWindow.setClippingEnabled(true); - } - - @Override - protected void initContentView() { - LinearLayout linearLayout = new LinearLayout(TextView.this.getContext()); - linearLayout.setOrientation(LinearLayout.HORIZONTAL); - mContentView = linearLayout; - mContentView.setBackgroundResource( - com.android.internal.R.drawable.text_edit_paste_window); - - LayoutInflater inflater = (LayoutInflater)TextView.this.mContext. - getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - LayoutParams wrapContent = new LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - mPasteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null); - mPasteTextView.setLayoutParams(wrapContent); - mContentView.addView(mPasteTextView); - mPasteTextView.setText(com.android.internal.R.string.paste); - mPasteTextView.setOnClickListener(this); - - mReplaceTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null); - mReplaceTextView.setLayoutParams(wrapContent); - mContentView.addView(mReplaceTextView); - mReplaceTextView.setText(com.android.internal.R.string.replace); - mReplaceTextView.setOnClickListener(this); - } - - @Override - public void show() { - boolean canPaste = canPaste(); - boolean canSuggest = isSuggestionsEnabled() && isCursorInsideSuggestionSpan(); - mPasteTextView.setVisibility(canPaste ? View.VISIBLE : View.GONE); - mReplaceTextView.setVisibility(canSuggest ? View.VISIBLE : View.GONE); - - if (!canPaste && !canSuggest) return; - - super.show(); - } - - @Override - public void onClick(View view) { - if (view == mPasteTextView && canPaste()) { - onTextContextMenuItem(ID_PASTE); - hide(); - } else if (view == mReplaceTextView) { - final int middle = (getSelectionStart() + getSelectionEnd()) / 2; - stopSelectionActionMode(); - Selection.setSelection((Spannable) mText, middle); - showSuggestions(); - } - } - - @Override - protected int getTextOffset() { - return (getSelectionStart() + getSelectionEnd()) / 2; - } - - @Override - protected int getVerticalLocalPosition(int line) { - return mLayout.getLineTop(line) - mContentView.getMeasuredHeight(); - } - - @Override - protected int clipVertically(int positionY) { - if (positionY < 0) { - final int offset = getTextOffset(); - final int line = mLayout.getLineForOffset(offset); - positionY += mLayout.getLineBottom(line) - mLayout.getLineTop(line); - positionY += mContentView.getMeasuredHeight(); - - // Assumes insertion and selection handles share the same height - final Drawable handle = mContext.getResources().getDrawable(mTextSelectHandleRes); - positionY += handle.getIntrinsicHeight(); - } - - return positionY; - } - } - - private abstract class HandleView extends View implements TextViewPositionListener { - protected Drawable mDrawable; - protected Drawable mDrawableLtr; - protected Drawable mDrawableRtl; - private final PopupWindow mContainer; - // Position with respect to the parent TextView - private int mPositionX, mPositionY; - private boolean mIsDragging; - // Offset from touch position to mPosition - private float mTouchToWindowOffsetX, mTouchToWindowOffsetY; - protected int mHotspotX; - // Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up - private float mTouchOffsetY; - // Where the touch position should be on the handle to ensure a maximum cursor visibility - private float mIdealVerticalOffset; - // Parent's (TextView) previous position in window - private int mLastParentX, mLastParentY; - // Transient action popup window for Paste and Replace actions - protected ActionPopupWindow mActionPopupWindow; - // Previous text character offset - private int mPreviousOffset = -1; - // Previous text character offset - private boolean mPositionHasChanged = true; - // Used to delay the appearance of the action popup window - private Runnable mActionPopupShower; - - public HandleView(Drawable drawableLtr, Drawable drawableRtl) { - super(TextView.this.mContext); - mContainer = new PopupWindow(TextView.this.mContext, null, - com.android.internal.R.attr.textSelectHandleWindowStyle); - mContainer.setSplitTouchEnabled(true); - mContainer.setClippingEnabled(false); - mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL); - mContainer.setContentView(this); - - mDrawableLtr = drawableLtr; - mDrawableRtl = drawableRtl; - - updateDrawable(); - - final int handleHeight = mDrawable.getIntrinsicHeight(); - mTouchOffsetY = -0.3f * handleHeight; - mIdealVerticalOffset = 0.7f * handleHeight; - } - - protected void updateDrawable() { - final int offset = getCurrentCursorOffset(); - final boolean isRtlCharAtOffset = mLayout.isRtlCharAt(offset); - mDrawable = isRtlCharAtOffset ? mDrawableRtl : mDrawableLtr; - mHotspotX = getHotspotX(mDrawable, isRtlCharAtOffset); - } - - protected abstract int getHotspotX(Drawable drawable, boolean isRtlRun); - - // Touch-up filter: number of previous positions remembered - private static final int HISTORY_SIZE = 5; - private static final int TOUCH_UP_FILTER_DELAY_AFTER = 150; - private static final int TOUCH_UP_FILTER_DELAY_BEFORE = 350; - private final long[] mPreviousOffsetsTimes = new long[HISTORY_SIZE]; - private final int[] mPreviousOffsets = new int[HISTORY_SIZE]; - private int mPreviousOffsetIndex = 0; - private int mNumberPreviousOffsets = 0; - - private void startTouchUpFilter(int offset) { - mNumberPreviousOffsets = 0; - addPositionToTouchUpFilter(offset); - } - - private void addPositionToTouchUpFilter(int offset) { - mPreviousOffsetIndex = (mPreviousOffsetIndex + 1) % HISTORY_SIZE; - mPreviousOffsets[mPreviousOffsetIndex] = offset; - mPreviousOffsetsTimes[mPreviousOffsetIndex] = SystemClock.uptimeMillis(); - mNumberPreviousOffsets++; - } - - private void filterOnTouchUp() { - final long now = SystemClock.uptimeMillis(); - int i = 0; - int index = mPreviousOffsetIndex; - final int iMax = Math.min(mNumberPreviousOffsets, HISTORY_SIZE); - while (i < iMax && (now - mPreviousOffsetsTimes[index]) < TOUCH_UP_FILTER_DELAY_AFTER) { - i++; - index = (mPreviousOffsetIndex - i + HISTORY_SIZE) % HISTORY_SIZE; - } - - if (i > 0 && i < iMax && - (now - mPreviousOffsetsTimes[index]) > TOUCH_UP_FILTER_DELAY_BEFORE) { - positionAtCursorOffset(mPreviousOffsets[index], false); - } - } - - public boolean offsetHasBeenChanged() { - return mNumberPreviousOffsets > 1; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()); - } - - public void show() { - if (isShowing()) return; - - getPositionListener().addSubscriber(this, true /* local position may change */); - - // Make sure the offset is always considered new, even when focusing at same position - mPreviousOffset = -1; - positionAtCursorOffset(getCurrentCursorOffset(), false); - - hideActionPopupWindow(); - } - - protected void dismiss() { - mIsDragging = false; - mContainer.dismiss(); - onDetached(); - } - - public void hide() { - dismiss(); - - TextView.this.getPositionListener().removeSubscriber(this); - } - - void showActionPopupWindow(int delay) { - if (mActionPopupWindow == null) { - mActionPopupWindow = new ActionPopupWindow(); - } - if (mActionPopupShower == null) { - mActionPopupShower = new Runnable() { - public void run() { - mActionPopupWindow.show(); - } - }; - } else { - TextView.this.removeCallbacks(mActionPopupShower); - } - TextView.this.postDelayed(mActionPopupShower, delay); - } - - protected void hideActionPopupWindow() { - if (mActionPopupShower != null) { - TextView.this.removeCallbacks(mActionPopupShower); - } - if (mActionPopupWindow != null) { - mActionPopupWindow.hide(); - } - } - - public boolean isShowing() { - return mContainer.isShowing(); - } - - private boolean isVisible() { - // Always show a dragging handle. - if (mIsDragging) { - return true; - } - - if (isInBatchEditMode()) { - return false; - } - - return TextView.this.isPositionVisible(mPositionX + mHotspotX, mPositionY); - } - - public abstract int getCurrentCursorOffset(); - - protected abstract void updateSelection(int offset); - - public abstract void updatePosition(float x, float y); - - protected void positionAtCursorOffset(int offset, boolean parentScrolled) { - // A HandleView relies on the layout, which may be nulled by external methods - if (mLayout == null) { - // Will update controllers' state, hiding them and stopping selection mode if needed - prepareCursorControllers(); - return; - } - - boolean offsetChanged = offset != mPreviousOffset; - if (offsetChanged || parentScrolled) { - if (offsetChanged) { - updateSelection(offset); - addPositionToTouchUpFilter(offset); - } - final int line = mLayout.getLineForOffset(offset); - - mPositionX = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5f - mHotspotX); - mPositionY = mLayout.getLineBottom(line); - - // Take TextView's padding and scroll into account. - mPositionX += viewportToContentHorizontalOffset(); - mPositionY += viewportToContentVerticalOffset(); - - mPreviousOffset = offset; - mPositionHasChanged = true; - } - } - - public void updatePosition(int parentPositionX, int parentPositionY, - boolean parentPositionChanged, boolean parentScrolled) { - positionAtCursorOffset(getCurrentCursorOffset(), parentScrolled); - if (parentPositionChanged || mPositionHasChanged) { - if (mIsDragging) { - // Update touchToWindow offset in case of parent scrolling while dragging - if (parentPositionX != mLastParentX || parentPositionY != mLastParentY) { - mTouchToWindowOffsetX += parentPositionX - mLastParentX; - mTouchToWindowOffsetY += parentPositionY - mLastParentY; - mLastParentX = parentPositionX; - mLastParentY = parentPositionY; - } - - onHandleMoved(); - } - - if (isVisible()) { - final int positionX = parentPositionX + mPositionX; - final int positionY = parentPositionY + mPositionY; - if (isShowing()) { - mContainer.update(positionX, positionY, -1, -1); - } else { - mContainer.showAtLocation(TextView.this, Gravity.NO_GRAVITY, - positionX, positionY); - } - } else { - if (isShowing()) { - dismiss(); - } - } - - mPositionHasChanged = false; - } - } - - @Override - protected void onDraw(Canvas c) { - mDrawable.setBounds(0, 0, mRight - mLeft, mBottom - mTop); - mDrawable.draw(c); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - switch (ev.getActionMasked()) { - case MotionEvent.ACTION_DOWN: { - startTouchUpFilter(getCurrentCursorOffset()); - mTouchToWindowOffsetX = ev.getRawX() - mPositionX; - mTouchToWindowOffsetY = ev.getRawY() - mPositionY; - - final PositionListener positionListener = getPositionListener(); - mLastParentX = positionListener.getPositionX(); - mLastParentY = positionListener.getPositionY(); - mIsDragging = true; - break; - } - - case MotionEvent.ACTION_MOVE: { - final float rawX = ev.getRawX(); - final float rawY = ev.getRawY(); - - // Vertical hysteresis: vertical down movement tends to snap to ideal offset - final float previousVerticalOffset = mTouchToWindowOffsetY - mLastParentY; - final float currentVerticalOffset = rawY - mPositionY - mLastParentY; - float newVerticalOffset; - if (previousVerticalOffset < mIdealVerticalOffset) { - newVerticalOffset = Math.min(currentVerticalOffset, mIdealVerticalOffset); - newVerticalOffset = Math.max(newVerticalOffset, previousVerticalOffset); - } else { - newVerticalOffset = Math.max(currentVerticalOffset, mIdealVerticalOffset); - newVerticalOffset = Math.min(newVerticalOffset, previousVerticalOffset); - } - mTouchToWindowOffsetY = newVerticalOffset + mLastParentY; - - final float newPosX = rawX - mTouchToWindowOffsetX + mHotspotX; - final float newPosY = rawY - mTouchToWindowOffsetY + mTouchOffsetY; - - updatePosition(newPosX, newPosY); - break; - } - - case MotionEvent.ACTION_UP: - filterOnTouchUp(); - mIsDragging = false; - break; - - case MotionEvent.ACTION_CANCEL: - mIsDragging = false; - break; - } - return true; - } - - public boolean isDragging() { - return mIsDragging; - } - - void onHandleMoved() { - hideActionPopupWindow(); - } - - public void onDetached() { - hideActionPopupWindow(); - } - } - - private class InsertionHandleView extends HandleView { - private static final int DELAY_BEFORE_HANDLE_FADES_OUT = 4000; - private static final int RECENT_CUT_COPY_DURATION = 15 * 1000; // seconds - - // Used to detect taps on the insertion handle, which will affect the ActionPopupWindow - private float mDownPositionX, mDownPositionY; - private Runnable mHider; - - public InsertionHandleView(Drawable drawable) { - super(drawable, drawable); - } - - @Override - public void show() { - super.show(); - - final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - LAST_CUT_OR_COPY_TIME; - if (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION) { - showActionPopupWindow(0); - } - - hideAfterDelay(); - } - - public void showWithActionPopup() { - show(); - showActionPopupWindow(0); - } - - private void hideAfterDelay() { - if (mHider == null) { - mHider = new Runnable() { - public void run() { - hide(); - } - }; - } else { - removeHiderCallback(); - } - TextView.this.postDelayed(mHider, DELAY_BEFORE_HANDLE_FADES_OUT); - } - - private void removeHiderCallback() { - if (mHider != null) { - TextView.this.removeCallbacks(mHider); - } - } - - @Override - protected int getHotspotX(Drawable drawable, boolean isRtlRun) { - return drawable.getIntrinsicWidth() / 2; - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - final boolean result = super.onTouchEvent(ev); - - switch (ev.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - mDownPositionX = ev.getRawX(); - mDownPositionY = ev.getRawY(); - break; - - case MotionEvent.ACTION_UP: - if (!offsetHasBeenChanged()) { - final float deltaX = mDownPositionX - ev.getRawX(); - final float deltaY = mDownPositionY - ev.getRawY(); - final float distanceSquared = deltaX * deltaX + deltaY * deltaY; - - final ViewConfiguration viewConfiguration = ViewConfiguration.get( - TextView.this.getContext()); - final int touchSlop = viewConfiguration.getScaledTouchSlop(); - - if (distanceSquared < touchSlop * touchSlop) { - if (mActionPopupWindow != null && mActionPopupWindow.isShowing()) { - // Tapping on the handle dismisses the displayed action popup - mActionPopupWindow.hide(); - } else { - showWithActionPopup(); - } - } - } - hideAfterDelay(); - break; - - case MotionEvent.ACTION_CANCEL: - hideAfterDelay(); - break; - - default: - break; - } - - return result; - } - - @Override - public int getCurrentCursorOffset() { - return TextView.this.getSelectionStart(); - } - - @Override - public void updateSelection(int offset) { - Selection.setSelection((Spannable) mText, offset); - } - - @Override - public void updatePosition(float x, float y) { - positionAtCursorOffset(getOffsetForPosition(x, y), false); - } - - @Override - void onHandleMoved() { - super.onHandleMoved(); - removeHiderCallback(); - } - - @Override - public void onDetached() { - super.onDetached(); - removeHiderCallback(); - } - } - - private class SelectionStartHandleView extends HandleView { - - public SelectionStartHandleView(Drawable drawableLtr, Drawable drawableRtl) { - super(drawableLtr, drawableRtl); - } - - @Override - protected int getHotspotX(Drawable drawable, boolean isRtlRun) { - if (isRtlRun) { - return drawable.getIntrinsicWidth() / 4; - } else { - return (drawable.getIntrinsicWidth() * 3) / 4; - } - } - - @Override - public int getCurrentCursorOffset() { - return TextView.this.getSelectionStart(); - } - - @Override - public void updateSelection(int offset) { - Selection.setSelection((Spannable) mText, offset, getSelectionEnd()); - updateDrawable(); - } - - @Override - public void updatePosition(float x, float y) { - int offset = getOffsetForPosition(x, y); - - // Handles can not cross and selection is at least one character - final int selectionEnd = getSelectionEnd(); - if (offset >= selectionEnd) offset = Math.max(0, selectionEnd - 1); - - positionAtCursorOffset(offset, false); - } - - public ActionPopupWindow getActionPopupWindow() { - return mActionPopupWindow; - } - } - - private class SelectionEndHandleView extends HandleView { - - public SelectionEndHandleView(Drawable drawableLtr, Drawable drawableRtl) { - super(drawableLtr, drawableRtl); - } - - @Override - protected int getHotspotX(Drawable drawable, boolean isRtlRun) { - if (isRtlRun) { - return (drawable.getIntrinsicWidth() * 3) / 4; - } else { - return drawable.getIntrinsicWidth() / 4; - } - } - - @Override - public int getCurrentCursorOffset() { - return TextView.this.getSelectionEnd(); - } - - @Override - public void updateSelection(int offset) { - Selection.setSelection((Spannable) mText, getSelectionStart(), offset); - updateDrawable(); - } - - @Override - public void updatePosition(float x, float y) { - int offset = getOffsetForPosition(x, y); - - // Handles can not cross and selection is at least one character - final int selectionStart = getSelectionStart(); - if (offset <= selectionStart) offset = Math.min(selectionStart + 1, mText.length()); - - positionAtCursorOffset(offset, false); - } - - public void setActionPopupWindow(ActionPopupWindow actionPopupWindow) { - mActionPopupWindow = actionPopupWindow; - } - } - - /** - * A CursorController instance can be used to control a cursor in the text. - * It is not used outside of {@link TextView}. - * @hide - */ - private interface CursorController extends ViewTreeObserver.OnTouchModeChangeListener { - /** - * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}. - * See also {@link #hide()}. - */ - public void show(); - - /** - * Hide the cursor controller from screen. - * See also {@link #show()}. - */ - public void hide(); - - /** - * Called when the view is detached from window. Perform house keeping task, such as - * stopping Runnable thread that would otherwise keep a reference on the context, thus - * preventing the activity from being recycled. - */ - public void onDetached(); - } - - private class InsertionPointCursorController implements CursorController { - private InsertionHandleView mHandle; - - public void show() { - getHandle().show(); - } - - public void showWithActionPopup() { - getHandle().showWithActionPopup(); - } - - public void hide() { - if (mHandle != null) { - mHandle.hide(); - } - } - - public void onTouchModeChanged(boolean isInTouchMode) { - if (!isInTouchMode) { - hide(); - } - } - - private InsertionHandleView getHandle() { - if (getEditor().mSelectHandleCenter == null) { - getEditor().mSelectHandleCenter = mContext.getResources().getDrawable( - mTextSelectHandleRes); - } - if (mHandle == null) { - mHandle = new InsertionHandleView(getEditor().mSelectHandleCenter); - } - return mHandle; - } - - @Override - public void onDetached() { - final ViewTreeObserver observer = getViewTreeObserver(); - observer.removeOnTouchModeChangeListener(this); - - if (mHandle != null) mHandle.onDetached(); - } - } - - private class SelectionModifierCursorController implements CursorController { - private static final int DELAY_BEFORE_REPLACE_ACTION = 200; // milliseconds - // The cursor controller handles, lazily created when shown. - private SelectionStartHandleView mStartHandle; - private SelectionEndHandleView mEndHandle; - // The offsets of that last touch down event. Remembered to start selection there. - private int mMinTouchOffset, mMaxTouchOffset; - - // Double tap detection - private long mPreviousTapUpTime = 0; - private float mDownPositionX, mDownPositionY; - private boolean mGestureStayedInTapRegion; - - SelectionModifierCursorController() { - resetTouchOffsets(); - } - - public void show() { - if (isInBatchEditMode()) { - return; - } - initDrawables(); - initHandles(); - hideInsertionPointCursorController(); - } - - private void initDrawables() { - if (getEditor().mSelectHandleLeft == null) { - getEditor().mSelectHandleLeft = mContext.getResources().getDrawable( - mTextSelectHandleLeftRes); - } - if (getEditor().mSelectHandleRight == null) { - getEditor().mSelectHandleRight = mContext.getResources().getDrawable( - mTextSelectHandleRightRes); - } - } - - private void initHandles() { - // Lazy object creation has to be done before updatePosition() is called. - if (mStartHandle == null) { - mStartHandle = new SelectionStartHandleView(getEditor().mSelectHandleLeft, getEditor().mSelectHandleRight); - } - if (mEndHandle == null) { - mEndHandle = new SelectionEndHandleView(getEditor().mSelectHandleRight, getEditor().mSelectHandleLeft); - } - - mStartHandle.show(); - mEndHandle.show(); - - // Make sure both left and right handles share the same ActionPopupWindow (so that - // moving any of the handles hides the action popup). - mStartHandle.showActionPopupWindow(DELAY_BEFORE_REPLACE_ACTION); - mEndHandle.setActionPopupWindow(mStartHandle.getActionPopupWindow()); - - hideInsertionPointCursorController(); - } - - public void hide() { - if (mStartHandle != null) mStartHandle.hide(); - if (mEndHandle != null) mEndHandle.hide(); - } - - public void onTouchEvent(MotionEvent event) { - // This is done even when the View does not have focus, so that long presses can start - // selection and tap can move cursor from this tap position. - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - final float x = event.getX(); - final float y = event.getY(); - - // Remember finger down position, to be able to start selection from there - mMinTouchOffset = mMaxTouchOffset = getOffsetForPosition(x, y); - - // Double tap detection - if (mGestureStayedInTapRegion) { - long duration = SystemClock.uptimeMillis() - mPreviousTapUpTime; - if (duration <= ViewConfiguration.getDoubleTapTimeout()) { - final float deltaX = x - mDownPositionX; - final float deltaY = y - mDownPositionY; - final float distanceSquared = deltaX * deltaX + deltaY * deltaY; - - ViewConfiguration viewConfiguration = ViewConfiguration.get( - TextView.this.getContext()); - int doubleTapSlop = viewConfiguration.getScaledDoubleTapSlop(); - boolean stayedInArea = distanceSquared < doubleTapSlop * doubleTapSlop; - - if (stayedInArea && isPositionOnText(x, y)) { - startSelectionActionMode(); - getEditor().mDiscardNextActionUp = true; - } - } - } - - mDownPositionX = x; - mDownPositionY = y; - mGestureStayedInTapRegion = true; - break; - - case MotionEvent.ACTION_POINTER_DOWN: - case MotionEvent.ACTION_POINTER_UP: - // Handle multi-point gestures. Keep min and max offset positions. - // Only activated for devices that correctly handle multi-touch. - if (mContext.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) { - updateMinAndMaxOffsets(event); - } - break; - - case MotionEvent.ACTION_MOVE: - if (mGestureStayedInTapRegion) { - final float deltaX = event.getX() - mDownPositionX; - final float deltaY = event.getY() - mDownPositionY; - final float distanceSquared = deltaX * deltaX + deltaY * deltaY; - - final ViewConfiguration viewConfiguration = ViewConfiguration.get( - TextView.this.getContext()); - int doubleTapTouchSlop = viewConfiguration.getScaledDoubleTapTouchSlop(); - - if (distanceSquared > doubleTapTouchSlop * doubleTapTouchSlop) { - mGestureStayedInTapRegion = false; - } - } - break; - - case MotionEvent.ACTION_UP: - mPreviousTapUpTime = SystemClock.uptimeMillis(); - break; - } - } - - /** - * @param event - */ - private void updateMinAndMaxOffsets(MotionEvent event) { - int pointerCount = event.getPointerCount(); - for (int index = 0; index < pointerCount; index++) { - int offset = getOffsetForPosition(event.getX(index), event.getY(index)); - if (offset < mMinTouchOffset) mMinTouchOffset = offset; - if (offset > mMaxTouchOffset) mMaxTouchOffset = offset; - } - } - - public int getMinTouchOffset() { - return mMinTouchOffset; - } - - public int getMaxTouchOffset() { - return mMaxTouchOffset; - } - - public void resetTouchOffsets() { - mMinTouchOffset = mMaxTouchOffset = -1; - } - - /** - * @return true iff this controller is currently used to move the selection start. - */ - public boolean isSelectionStartDragged() { - return mStartHandle != null && mStartHandle.isDragging(); - } - - public void onTouchModeChanged(boolean isInTouchMode) { - if (!isInTouchMode) { - hide(); - } - } - - @Override - public void onDetached() { - final ViewTreeObserver observer = getViewTreeObserver(); - observer.removeOnTouchModeChangeListener(this); - - if (mStartHandle != null) mStartHandle.onDetached(); - if (mEndHandle != null) mEndHandle.onDetached(); - } - } - - static class InputContentType { - int imeOptions = EditorInfo.IME_NULL; - String privateImeOptions; - CharSequence imeActionLabel; - int imeActionId; - Bundle extras; - OnEditorActionListener onEditorActionListener; - boolean enterDown; - } - - static class InputMethodState { - Rect mCursorRectInWindow = new Rect(); - RectF mTmpRectF = new RectF(); - float[] mTmpOffset = new float[2]; - ExtractedTextRequest mExtracting; - final ExtractedText mTmpExtracted = new ExtractedText(); - int mBatchEditNesting; - boolean mCursorChanged; - boolean mSelectionModeChanged; - boolean mContentChanged; - int mChangedStart, mChangedEnd, mChangedDelta; - } - - private class Editor { - // Cursor Controllers. - InsertionPointCursorController mInsertionPointCursorController; - SelectionModifierCursorController mSelectionModifierCursorController; - ActionMode mSelectionActionMode; - boolean mInsertionControllerEnabled; - boolean mSelectionControllerEnabled; - - // Used to highlight a word when it is corrected by the IME - CorrectionHighlighter mCorrectionHighlighter; - - InputContentType mInputContentType; - InputMethodState mInputMethodState; - - DisplayList[] mTextDisplayLists; - - boolean mFrozenWithFocus; - boolean mSelectionMoved; - boolean mTouchFocusSelected; - - KeyListener mKeyListener; - int mInputType = EditorInfo.TYPE_NULL; - - boolean mDiscardNextActionUp; - boolean mIgnoreActionUpEvent; - - long mShowCursor; - Blink mBlink; - - boolean mCursorVisible = true; - boolean mSelectAllOnFocus; - boolean mTextIsSelectable; - - CharSequence mError; - boolean mErrorWasChanged; - ErrorPopup mErrorPopup; - /** - * This flag is set if the TextView tries to display an error before it - * is attached to the window (so its position is still unknown). - * It causes the error to be shown later, when onAttachedToWindow() - * is called. - */ - boolean mShowErrorAfterAttach; - - boolean mInBatchEditControllers; - - SuggestionsPopupWindow mSuggestionsPopupWindow; - SuggestionRangeSpan mSuggestionRangeSpan; - Runnable mShowSuggestionRunnable; - - final Drawable[] mCursorDrawable = new Drawable[2]; - int mCursorCount; // Current number of used mCursorDrawable: 0 (resource=0), 1 or 2 (split) - - Drawable mSelectHandleLeft; - Drawable mSelectHandleRight; - Drawable mSelectHandleCenter; - - // Global listener that detects changes in the global position of the TextView - PositionListener mPositionListener; - - float mLastDownPositionX, mLastDownPositionY; - Callback mCustomSelectionActionModeCallback; - - // Set when this TextView gained focus with some text selected. Will start selection mode. - boolean mCreatedWithASelection; - - WordIterator mWordIterator; - SpellChecker mSpellChecker; - - void onAttachedToWindow() { - final ViewTreeObserver observer = getViewTreeObserver(); - // No need to create the controller. - // The get method will add the listener on controller creation. - if (mInsertionPointCursorController != null) { - observer.addOnTouchModeChangeListener(mInsertionPointCursorController); - } - if (mSelectionModifierCursorController != null) { - observer.addOnTouchModeChangeListener(mSelectionModifierCursorController); - } - updateSpellCheckSpans(0, mText.length(), true /* create the spell checker if needed */); - } - - void onDetachedFromWindow() { - if (mError != null) { - hideError(); - } - - if (mBlink != null) { - mBlink.removeCallbacks(mBlink); - } - - if (mInsertionPointCursorController != null) { - mInsertionPointCursorController.onDetached(); - } - - if (mSelectionModifierCursorController != null) { - mSelectionModifierCursorController.onDetached(); - } - - if (mShowSuggestionRunnable != null) { - removeCallbacks(mShowSuggestionRunnable); - } - - invalidateTextDisplayList(); - - if (mSpellChecker != null) { - mSpellChecker.closeSession(); - // Forces the creation of a new SpellChecker next time this window is created. - // Will handle the cases where the settings has been changed in the meantime. - mSpellChecker = null; - } - - hideControllers(); - } - - void onScreenStateChanged(int screenState) { - switch (screenState) { - case SCREEN_STATE_ON: - resumeBlink(); - break; - case SCREEN_STATE_OFF: - suspendBlink(); - break; - } - } - - private void suspendBlink() { - if (mBlink != null) { - mBlink.cancel(); - } - } - - private void resumeBlink() { - if (mBlink != null) { - mBlink.uncancel(); - makeBlink(); - } - } - - void adjustInputType(boolean password, boolean passwordInputType, - boolean webPasswordInputType, boolean numberPasswordInputType) { - // mInputType has been set from inputType, possibly modified by mInputMethod. - // Specialize mInputType to [web]password if we have a text class and the original input - // type was a password. - if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { - if (password || passwordInputType) { - mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION)) - | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; - } - if (webPasswordInputType) { - mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION)) - | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD; - } - } else if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_NUMBER) { - if (numberPasswordInputType) { - mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION)) - | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD; - } - } - } - - void setFrame() { - if (mErrorPopup != null) { - TextView tv = (TextView) mErrorPopup.getContentView(); - chooseSize(mErrorPopup, mError, tv); - mErrorPopup.update(TextView.this, getErrorX(), getErrorY(), - mErrorPopup.getWidth(), mErrorPopup.getHeight()); - } - } - - void onFocusChanged(boolean focused, int direction) { - mShowCursor = SystemClock.uptimeMillis(); - ensureEndedBatchEdit(); - - if (focused) { - int selStart = getSelectionStart(); - int selEnd = getSelectionEnd(); - - // SelectAllOnFocus fields are highlighted and not selected. Do not start text selection - // mode for these, unless there was a specific selection already started. - final boolean isFocusHighlighted = mSelectAllOnFocus && selStart == 0 && - selEnd == mText.length(); - - mCreatedWithASelection = mFrozenWithFocus && hasSelection() && !isFocusHighlighted; - - if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) { - // If a tap was used to give focus to that view, move cursor at tap position. - // Has to be done before onTakeFocus, which can be overloaded. - final int lastTapPosition = getLastTapPosition(); - if (lastTapPosition >= 0) { - Selection.setSelection((Spannable) mText, lastTapPosition); - } - - // Note this may have to be moved out of the Editor class - if (mMovement != null) { - mMovement.onTakeFocus(TextView.this, (Spannable) mText, direction); - } - - // The DecorView does not have focus when the 'Done' ExtractEditText button is - // pressed. Since it is the ViewAncestor's mView, it requests focus before - // ExtractEditText clears focus, which gives focus to the ExtractEditText. - // This special case ensure that we keep current selection in that case. - // It would be better to know why the DecorView does not have focus at that time. - if (((TextView.this instanceof ExtractEditText) || mSelectionMoved) && - selStart >= 0 && selEnd >= 0) { - /* - * Someone intentionally set the selection, so let them - * do whatever it is that they wanted to do instead of - * the default on-focus behavior. We reset the selection - * here instead of just skipping the onTakeFocus() call - * because some movement methods do something other than - * just setting the selection in theirs and we still - * need to go through that path. - */ - Selection.setSelection((Spannable) mText, selStart, selEnd); - } - - if (mSelectAllOnFocus) { - selectAll(); - } - - mTouchFocusSelected = true; - } - - mFrozenWithFocus = false; - mSelectionMoved = false; - - if (mError != null) { - showError(); - } - - makeBlink(); - } else { - if (mError != null) { - hideError(); - } - // Don't leave us in the middle of a batch edit. - onEndBatchEdit(); - - if (TextView.this instanceof ExtractEditText) { - // terminateTextSelectionMode removes selection, which we want to keep when - // ExtractEditText goes out of focus. - final int selStart = getSelectionStart(); - final int selEnd = getSelectionEnd(); - hideControllers(); - Selection.setSelection((Spannable) mText, selStart, selEnd); - } else { - hideControllers(); - downgradeEasyCorrectionSpans(); - } - - // No need to create the controller - if (mSelectionModifierCursorController != null) { - mSelectionModifierCursorController.resetTouchOffsets(); - } - } - } - - void sendOnTextChanged(int start, int after) { - updateSpellCheckSpans(start, start + after, false); - - // Hide the controllers as soon as text is modified (typing, procedural...) - // We do not hide the span controllers, since they can be added when a new text is - // inserted into the text view (voice IME). - hideCursorControllers(); - } - - private int getLastTapPosition() { - // No need to create the controller at that point, no last tap position saved - if (mSelectionModifierCursorController != null) { - int lastTapPosition = mSelectionModifierCursorController.getMinTouchOffset(); - if (lastTapPosition >= 0) { - // Safety check, should not be possible. - if (lastTapPosition > mText.length()) { - Log.e(LOG_TAG, "Invalid tap focus position (" + lastTapPosition + " vs " - + mText.length() + ")"); - lastTapPosition = mText.length(); - } - return lastTapPosition; - } - } - - return -1; - } - - void onWindowFocusChanged(boolean hasWindowFocus) { - if (hasWindowFocus) { - if (mBlink != null) { - mBlink.uncancel(); - makeBlink(); - } - } else { - if (mBlink != null) { - mBlink.cancel(); - } - if (mInputContentType != null) { - mInputContentType.enterDown = false; - } - // Order matters! Must be done before onParentLostFocus to rely on isShowingUp - hideControllers(); - if (mSuggestionsPopupWindow != null) { - mSuggestionsPopupWindow.onParentLostFocus(); - } - - // Don't leave us in the middle of a batch edit. - onEndBatchEdit(); - } - } - - void onTouchEvent(MotionEvent event) { - if (hasSelectionController()) { - getSelectionController().onTouchEvent(event); - } - - if (mShowSuggestionRunnable != null) { - removeCallbacks(mShowSuggestionRunnable); - mShowSuggestionRunnable = null; - } - - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mLastDownPositionX = event.getX(); - mLastDownPositionY = event.getY(); - - // Reset this state; it will be re-set if super.onTouchEvent - // causes focus to move to the view. - mTouchFocusSelected = false; - mIgnoreActionUpEvent = false; - } - } - - void onDraw(Canvas canvas, Layout layout, Path highlight, int cursorOffsetVertical) { - final int selectionStart = getSelectionStart(); - final int selectionEnd = getSelectionEnd(); - - final InputMethodState ims = mInputMethodState; - if (ims != null && ims.mBatchEditNesting == 0) { - InputMethodManager imm = InputMethodManager.peekInstance(); - if (imm != null) { - if (imm.isActive(TextView.this)) { - boolean reported = false; - if (ims.mContentChanged || ims.mSelectionModeChanged) { - // We are in extract mode and the content has changed - // in some way... just report complete new text to the - // input method. - reported = reportExtractedText(); - } - if (!reported && highlight != null) { - int candStart = -1; - int candEnd = -1; - if (mText instanceof Spannable) { - Spannable sp = (Spannable)mText; - candStart = EditableInputConnection.getComposingSpanStart(sp); - candEnd = EditableInputConnection.getComposingSpanEnd(sp); - } - imm.updateSelection(TextView.this, - selectionStart, selectionEnd, candStart, candEnd); - } - } - - if (imm.isWatchingCursor(TextView.this) && highlight != null) { - highlight.computeBounds(ims.mTmpRectF, true); - ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0; - - canvas.getMatrix().mapPoints(ims.mTmpOffset); - ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]); - - ims.mTmpRectF.offset(0, cursorOffsetVertical); - - ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5), - (int)(ims.mTmpRectF.top + 0.5), - (int)(ims.mTmpRectF.right + 0.5), - (int)(ims.mTmpRectF.bottom + 0.5)); - - imm.updateCursor(TextView.this, - ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top, - ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom); - } - } - } - - if (mCorrectionHighlighter != null) { - mCorrectionHighlighter.draw(canvas, cursorOffsetVertical); - } - - if (highlight != null && selectionStart == selectionEnd && mCursorCount > 0) { - drawCursor(canvas, cursorOffsetVertical); - // Rely on the drawable entirely, do not draw the cursor line. - // Has to be done after the IMM related code above which relies on the highlight. - highlight = null; - } - - if (canHaveDisplayList() && canvas.isHardwareAccelerated()) { - drawHardwareAccelerated(canvas, layout, highlight, cursorOffsetVertical); - } else { - layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); - } - - if (mMarquee != null && mMarquee.shouldDrawGhost()) { - canvas.translate((int) mMarquee.getGhostOffset(), 0.0f); - layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); - } - } - - private void drawHardwareAccelerated(Canvas canvas, Layout layout, Path highlight, - int cursorOffsetVertical) { - final int width = mRight - mLeft; - final int height = mBottom - mTop; - - final long lineRange = layout.getLineRangeForDraw(canvas); - int firstLine = TextUtils.unpackRangeStartFromLong(lineRange); - int lastLine = TextUtils.unpackRangeEndFromLong(lineRange); - if (lastLine < 0) return; - - layout.drawBackground(canvas, highlight, mHighlightPaint, cursorOffsetVertical, - firstLine, lastLine); - - if (layout instanceof DynamicLayout) { - if (mTextDisplayLists == null) { - mTextDisplayLists = new DisplayList[ArrayUtils.idealObjectArraySize(0)]; - } - - DynamicLayout dynamicLayout = (DynamicLayout) layout; - int[] blockEnds = dynamicLayout.getBlockEnds(); - int[] blockIndices = dynamicLayout.getBlockIndices(); - final int numberOfBlocks = dynamicLayout.getNumberOfBlocks(); - - canvas.translate(mScrollX, mScrollY); - int endOfPreviousBlock = -1; - int searchStartIndex = 0; - for (int i = 0; i < numberOfBlocks; i++) { - int blockEnd = blockEnds[i]; - int blockIndex = blockIndices[i]; - - final boolean blockIsInvalid = blockIndex == DynamicLayout.INVALID_BLOCK_INDEX; - if (blockIsInvalid) { - blockIndex = getAvailableDisplayListIndex(blockIndices, numberOfBlocks, - searchStartIndex); - // Dynamic layout internal block indices structure is updated from Editor - blockIndices[i] = blockIndex; - searchStartIndex = blockIndex + 1; - } - - DisplayList blockDisplayList = mTextDisplayLists[blockIndex]; - if (blockDisplayList == null) { - blockDisplayList = mTextDisplayLists[blockIndex] = - getHardwareRenderer().createDisplayList("Text " + blockIndex); - } else { - if (blockIsInvalid) blockDisplayList.invalidate(); - } - - if (!blockDisplayList.isValid()) { - final HardwareCanvas hardwareCanvas = blockDisplayList.start(); - try { - hardwareCanvas.setViewport(width, height); - // The dirty rect should always be null for a display list - hardwareCanvas.onPreDraw(null); - hardwareCanvas.translate(-mScrollX, -mScrollY); - layout.drawText(hardwareCanvas, endOfPreviousBlock + 1, blockEnd); - hardwareCanvas.translate(mScrollX, mScrollY); - } finally { - hardwareCanvas.onPostDraw(); - blockDisplayList.end(); - if (USE_DISPLAY_LIST_PROPERTIES) { - blockDisplayList.setLeftTopRightBottom(0, 0, width, height); - } - } - } - - ((HardwareCanvas) canvas).drawDisplayList(blockDisplayList, width, height, null, - DisplayList.FLAG_CLIP_CHILDREN); - endOfPreviousBlock = blockEnd; - } - canvas.translate(-mScrollX, -mScrollY); - } else { - // Fallback on the layout method (a BoringLayout is used when the text is empty) - layout.drawText(canvas, firstLine, lastLine); - } - } - - private int getAvailableDisplayListIndex(int[] blockIndices, int numberOfBlocks, - int searchStartIndex) { - int length = mTextDisplayLists.length; - for (int i = searchStartIndex; i < length; i++) { - boolean blockIndexFound = false; - for (int j = 0; j < numberOfBlocks; j++) { - if (blockIndices[j] == i) { - blockIndexFound = true; - break; - } - } - if (blockIndexFound) continue; - return i; - } - - // No available index found, the pool has to grow - int newSize = ArrayUtils.idealIntArraySize(length + 1); - DisplayList[] displayLists = new DisplayList[newSize]; - System.arraycopy(mTextDisplayLists, 0, displayLists, 0, length); - mTextDisplayLists = displayLists; - return length; - } - - private void drawCursor(Canvas canvas, int cursorOffsetVertical) { - final boolean translate = cursorOffsetVertical != 0; - if (translate) canvas.translate(0, cursorOffsetVertical); - for (int i = 0; i < getEditor().mCursorCount; i++) { - mCursorDrawable[i].draw(canvas); - } - if (translate) canvas.translate(0, -cursorOffsetVertical); - } - - private void invalidateTextDisplayList() { - if (mTextDisplayLists != null) { - for (int i = 0; i < mTextDisplayLists.length; i++) { - if (mTextDisplayLists[i] != null) mTextDisplayLists[i].invalidate(); - } - } - } - - private void updateCursorsPositions() { - if (mCursorDrawableRes == 0) { - mCursorCount = 0; - return; - } - - final int offset = getSelectionStart(); - final int line = mLayout.getLineForOffset(offset); - final int top = mLayout.getLineTop(line); - final int bottom = mLayout.getLineTop(line + 1); - - mCursorCount = mLayout.isLevelBoundary(offset) ? 2 : 1; - - int middle = bottom; - if (mCursorCount == 2) { - // Similar to what is done in {@link Layout.#getCursorPath(int, Path, CharSequence)} - middle = (top + bottom) >> 1; - } - - updateCursorPosition(0, top, middle, mLayout.getPrimaryHorizontal(offset)); - - if (mCursorCount == 2) { - updateCursorPosition(1, middle, bottom, mLayout.getSecondaryHorizontal(offset)); - } - } - - private void updateCursorPosition(int cursorIndex, int top, int bottom, float horizontal) { - if (mCursorDrawable[cursorIndex] == null) - mCursorDrawable[cursorIndex] = mContext.getResources().getDrawable(mCursorDrawableRes); - - if (mTempRect == null) mTempRect = new Rect(); - mCursorDrawable[cursorIndex].getPadding(mTempRect); - final int width = mCursorDrawable[cursorIndex].getIntrinsicWidth(); - horizontal = Math.max(0.5f, horizontal - 0.5f); - final int left = (int) (horizontal) - mTempRect.left; - mCursorDrawable[cursorIndex].setBounds(left, top - mTempRect.top, left + width, - bottom + mTempRect.bottom); - } } } diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java index f3486bdabb99..3115effd79dc 100644 --- a/core/java/com/android/internal/app/ActionBarImpl.java +++ b/core/java/com/android/internal/app/ActionBarImpl.java @@ -21,6 +21,7 @@ import com.android.internal.view.menu.MenuPopupHelper; import com.android.internal.view.menu.SubMenuBuilder; import com.android.internal.widget.ActionBarContainer; import com.android.internal.widget.ActionBarContextView; +import com.android.internal.widget.ActionBarOverlayLayout; import com.android.internal.widget.ActionBarView; import com.android.internal.widget.ScrollingTabContainerView; @@ -47,6 +48,7 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; +import android.view.ViewGroup; import android.view.Window; import android.view.accessibility.AccessibilityEvent; import android.widget.SpinnerAdapter; @@ -69,7 +71,9 @@ public class ActionBarImpl extends ActionBar { private Activity mActivity; private Dialog mDialog; + private ActionBarOverlayLayout mOverlayLayout; private ActionBarContainer mContainerView; + private ViewGroup mTopVisibilityView; private ActionBarView mActionView; private ActionBarContextView mContextView; private ActionBarContainer mSplitView; @@ -100,6 +104,8 @@ public class ActionBarImpl extends ActionBar { final Handler mHandler = new Handler(); Runnable mTabSelector; + private int mCurWindowVisibility = View.VISIBLE; + private Animator mCurrentShowAnim; private Animator mCurrentModeAnim; private boolean mShowHideAnimationEnabled; @@ -110,12 +116,12 @@ public class ActionBarImpl extends ActionBar { public void onAnimationEnd(Animator animation) { if (mContentView != null) { mContentView.setTranslationY(0); - mContainerView.setTranslationY(0); + mTopVisibilityView.setTranslationY(0); } if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) { mSplitView.setVisibility(View.GONE); } - mContainerView.setVisibility(View.GONE); + mTopVisibilityView.setVisibility(View.GONE); mContainerView.setTransitioning(false); mCurrentShowAnim = null; completeDeferredDestroyActionMode(); @@ -126,7 +132,7 @@ public class ActionBarImpl extends ActionBar { @Override public void onAnimationEnd(Animator animation) { mCurrentShowAnim = null; - mContainerView.requestLayout(); + mTopVisibilityView.requestLayout(); } }; @@ -147,11 +153,21 @@ public class ActionBarImpl extends ActionBar { private void init(View decor) { mContext = decor.getContext(); + mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById( + com.android.internal.R.id.action_bar_overlay_layout); + if (mOverlayLayout != null) { + mOverlayLayout.setActionBar(this); + } mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar); mContextView = (ActionBarContextView) decor.findViewById( com.android.internal.R.id.action_context_bar); mContainerView = (ActionBarContainer) decor.findViewById( com.android.internal.R.id.action_bar_container); + mTopVisibilityView = (ViewGroup)decor.findViewById( + com.android.internal.R.id.top_action_bar); + if (mTopVisibilityView == null) { + mTopVisibilityView = mContainerView; + } mSplitView = (ActionBarContainer) decor.findViewById( com.android.internal.R.id.split_action_bar); @@ -190,11 +206,22 @@ public class ActionBarImpl extends ActionBar { } final boolean isInTabMode = getNavigationMode() == NAVIGATION_MODE_TABS; if (mTabScrollView != null) { - mTabScrollView.setVisibility(isInTabMode ? View.VISIBLE : View.GONE); + if (isInTabMode) { + mTabScrollView.setVisibility(View.VISIBLE); + if (mOverlayLayout != null) { + mOverlayLayout.requestFitSystemWindows(); + } + } else { + mTabScrollView.setVisibility(View.GONE); + } } mActionView.setCollapsable(!mHasEmbeddedTabs && isInTabMode); } + public boolean hasNonEmbeddedTabs() { + return !mHasEmbeddedTabs && getNavigationMode() == NAVIGATION_MODE_TABS; + } + private void ensureTabsExist() { if (mTabScrollView != null) { return; @@ -206,8 +233,14 @@ public class ActionBarImpl extends ActionBar { tabScroller.setVisibility(View.VISIBLE); mActionView.setEmbeddedTabView(tabScroller); } else { - tabScroller.setVisibility(getNavigationMode() == NAVIGATION_MODE_TABS ? - View.VISIBLE : View.GONE); + if (getNavigationMode() == NAVIGATION_MODE_TABS) { + tabScroller.setVisibility(View.VISIBLE); + if (mOverlayLayout != null) { + mOverlayLayout.requestFitSystemWindows(); + } + } else { + tabScroller.setVisibility(View.GONE); + } mContainerView.setTabContainer(tabScroller); } mTabScrollView = tabScroller; @@ -221,6 +254,10 @@ public class ActionBarImpl extends ActionBar { } } + public void setWindowVisibility(int visibility) { + mCurWindowVisibility = visibility; + } + /** * Enables or disables animation between show/hide states. * If animation is disabled using this method, animations in progress @@ -396,7 +433,12 @@ public class ActionBarImpl extends ActionBar { animateToMode(true); if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) { // TODO animate this - mSplitView.setVisibility(View.VISIBLE); + if (mSplitView.getVisibility() != View.VISIBLE) { + mSplitView.setVisibility(View.VISIBLE); + if (mOverlayLayout != null) { + mOverlayLayout.requestFitSystemWindows(); + } + } } mContextView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); mActionMode = mode; @@ -530,28 +572,29 @@ public class ActionBarImpl extends ActionBar { @Override public void show() { - show(true); + show(true, false); } - void show(boolean markHiddenBeforeMode) { + public void show(boolean markHiddenBeforeMode, boolean alwaysAnimate) { if (mCurrentShowAnim != null) { mCurrentShowAnim.end(); } - if (mContainerView.getVisibility() == View.VISIBLE) { + if (mTopVisibilityView.getVisibility() == View.VISIBLE) { if (markHiddenBeforeMode) mWasHiddenBeforeMode = false; return; } - mContainerView.setVisibility(View.VISIBLE); + mTopVisibilityView.setVisibility(View.VISIBLE); - if (mShowHideAnimationEnabled) { - mContainerView.setAlpha(0); + if (mCurWindowVisibility == View.VISIBLE && (mShowHideAnimationEnabled + || alwaysAnimate)) { + mTopVisibilityView.setAlpha(0); AnimatorSet anim = new AnimatorSet(); - AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mContainerView, "alpha", 1)); + AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mTopVisibilityView, "alpha", 1)); if (mContentView != null) { b.with(ObjectAnimator.ofFloat(mContentView, "translationY", - -mContainerView.getHeight(), 0)); - mContainerView.setTranslationY(-mContainerView.getHeight()); - b.with(ObjectAnimator.ofFloat(mContainerView, "translationY", 0)); + -mTopVisibilityView.getHeight(), 0)); + mTopVisibilityView.setTranslationY(-mTopVisibilityView.getHeight()); + b.with(ObjectAnimator.ofFloat(mTopVisibilityView, "translationY", 0)); } if (mSplitView != null && mContextDisplayMode == CONTEXT_DISPLAY_SPLIT) { mSplitView.setAlpha(0); @@ -562,7 +605,7 @@ public class ActionBarImpl extends ActionBar { mCurrentShowAnim = anim; anim.start(); } else { - mContainerView.setAlpha(1); + mTopVisibilityView.setAlpha(1); mContainerView.setTranslationY(0); mShowListener.onAnimationEnd(null); } @@ -570,23 +613,28 @@ public class ActionBarImpl extends ActionBar { @Override public void hide() { + hide(false); + } + + public void hide(boolean alwaysAnimate) { if (mCurrentShowAnim != null) { mCurrentShowAnim.end(); } - if (mContainerView.getVisibility() == View.GONE) { + if (mTopVisibilityView.getVisibility() == View.GONE) { return; } - if (mShowHideAnimationEnabled) { - mContainerView.setAlpha(1); + if (mCurWindowVisibility == View.VISIBLE && (mShowHideAnimationEnabled + || alwaysAnimate)) { + mTopVisibilityView.setAlpha(1); mContainerView.setTransitioning(true); AnimatorSet anim = new AnimatorSet(); - AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mContainerView, "alpha", 0)); + AnimatorSet.Builder b = anim.play(ObjectAnimator.ofFloat(mTopVisibilityView, "alpha", 0)); if (mContentView != null) { b.with(ObjectAnimator.ofFloat(mContentView, "translationY", - 0, -mContainerView.getHeight())); - b.with(ObjectAnimator.ofFloat(mContainerView, "translationY", - -mContainerView.getHeight())); + 0, -mTopVisibilityView.getHeight())); + b.with(ObjectAnimator.ofFloat(mTopVisibilityView, "translationY", + -mTopVisibilityView.getHeight())); } if (mSplitView != null && mSplitView.getVisibility() == View.VISIBLE) { mSplitView.setAlpha(1); @@ -601,12 +649,12 @@ public class ActionBarImpl extends ActionBar { } public boolean isShowing() { - return mContainerView.getVisibility() == View.VISIBLE; + return mTopVisibilityView.getVisibility() == View.VISIBLE; } void animateToMode(boolean toActionMode) { if (toActionMode) { - show(false); + show(false, false); } if (mCurrentModeAnim != null) { mCurrentModeAnim.end(); @@ -980,6 +1028,11 @@ public class ActionBarImpl extends ActionBar { mTabScrollView.setVisibility(View.GONE); break; } + if (oldMode != mode && !mHasEmbeddedTabs) { + if (mOverlayLayout != null) { + mOverlayLayout.requestFitSystemWindows(); + } + } mActionView.setNavigationMode(mode); switch (mode) { case NAVIGATION_MODE_TABS: diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index aca1fa2043aa..294d4c44028c 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -30,10 +30,12 @@ oneway interface IStatusBar void disable(int state); void animateExpand(); void animateCollapse(); - void setSystemUiVisibility(int vis); + void setSystemUiVisibility(int vis, int mask); void topAppWindowChanged(boolean menuVisible); void setImeWindowStatus(in IBinder token, int vis, int backDisposition); void setHardKeyboardStatus(boolean available, boolean enabled); void toggleRecentApps(); + void preloadRecentApps(); + void cancelPreloadRecentApps(); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index ecebfc0b32e9..c64f1700d257 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -44,7 +44,9 @@ interface IStatusBarService int uid, int initialPid, String message); void onClearAllNotifications(); void onNotificationClear(String pkg, String tag, int id); - void setSystemUiVisibility(int vis); + void setSystemUiVisibility(int vis, int mask); void setHardKeyboardEnabled(boolean enabled); void toggleRecentApps(); + void preloadRecentApps(); + void cancelPreloadRecentApps(); } diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java new file mode 100644 index 000000000000..85214814a230 --- /dev/null +++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.widget; + +import com.android.internal.app.ActionBarImpl; + +import android.animation.LayoutTransition; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.view.ViewTreeObserver; +import android.widget.FrameLayout; + +/** + * Special layout for the containing of an overlay action bar (and its + * content) to correctly handle fitting system windows when the content + * has request that its layout ignore them. + */ +public class ActionBarOverlayLayout extends FrameLayout { + private int mActionBarHeight; + private ActionBarImpl mActionBar; + private int mWindowVisibility = View.VISIBLE; + private View mContent; + private View mActionBarTop; + private ActionBarContainer mContainerView; + private ActionBarView mActionView; + private View mActionBarBottom; + private int mLastSystemUiVisibility; + private final Rect mZeroRect = new Rect(0, 0, 0, 0); + + static final int[] mActionBarSizeAttr = new int [] { + com.android.internal.R.attr.actionBarSize + }; + + public ActionBarOverlayLayout(Context context) { + super(context); + init(context); + } + + public ActionBarOverlayLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context); + } + + private void init(Context context) { + TypedArray ta = getContext().getTheme().obtainStyledAttributes(mActionBarSizeAttr); + mActionBarHeight = ta.getDimensionPixelSize(0, 0); + ta.recycle(); + } + + public void setActionBar(ActionBarImpl impl) { + mActionBar = impl; + if (getWindowToken() != null) { + // This is being initialized after being added to a window; + // make sure to update all state now. + mActionBar.setWindowVisibility(mWindowVisibility); + if (mLastSystemUiVisibility != 0) { + int newVis = mLastSystemUiVisibility; + onWindowSystemUiVisibilityChanged(newVis); + requestFitSystemWindows(); + } + } + } + + @Override + public void onWindowSystemUiVisibilityChanged(int visible) { + super.onWindowSystemUiVisibilityChanged(visible); + pullChildren(); + final int diff = mLastSystemUiVisibility ^ visible; + mLastSystemUiVisibility = visible; + final boolean barVisible = (visible&SYSTEM_UI_FLAG_FULLSCREEN) == 0; + final boolean wasVisible = mActionBar != null ? mActionBar.isShowing() : true; + if (barVisible != wasVisible || (diff&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) { + if (mActionBar != null) { + if (barVisible) mActionBar.show(true, true); + else mActionBar.hide(true); + requestFitSystemWindows(); + } + } + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + mWindowVisibility = visibility; + if (mActionBar != null) { + mActionBar.setWindowVisibility(visibility); + } + } + + private boolean applyInsets(View view, Rect insets, boolean left, boolean top, + boolean bottom, boolean right) { + boolean changed = false; + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams)view.getLayoutParams(); + if (left && lp.leftMargin != insets.left) { + changed = true; + lp.leftMargin = insets.left; + } + if (top && lp.topMargin != insets.top) { + changed = true; + lp.topMargin = insets.top; + } + if (right && lp.rightMargin != insets.right) { + changed = true; + lp.rightMargin = insets.right; + } + if (bottom && lp.bottomMargin != insets.bottom) { + changed = true; + lp.bottomMargin = insets.bottom; + } + return changed; + } + + @Override + protected boolean fitSystemWindows(Rect insets) { + pullChildren(); + + final int vis = getWindowSystemUiVisibility(); + final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; + + // The top and bottom action bars are always within the content area. + boolean changed = applyInsets(mActionBarTop, insets, true, true, false, true); + if (mActionBarBottom != null) { + changed |= applyInsets(mActionBarBottom, insets, true, false, true, true); + } + + // If the window has not requested system UI layout flags, we need to + // make sure its content is not being covered by system UI... though it + // will still be covered by the action bar since they have requested it to + // overlay. + if ((vis & SYSTEM_UI_LAYOUT_FLAGS) == 0) { + changed |= applyInsets(mContent, insets, true, true, true, true); + // The insets are now consumed. + insets.set(0, 0, 0, 0); + } else { + changed |= applyInsets(mContent, mZeroRect, true, true, true, true); + } + + + if (stable || mActionBarTop.getVisibility() == VISIBLE) { + // The action bar creates additional insets for its content to use. + insets.top += mActionBarHeight; + } + + if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) { + View tabs = mContainerView.getTabContainer(); + if (stable || (tabs != null && tabs.getVisibility() == VISIBLE)) { + // If tabs are not embedded, adjust insets to account for them. + insets.top += mActionBarHeight; + } + } + + if (mActionView.isSplitActionBar()) { + if (stable || (mActionBarBottom != null + && mActionBarBottom.getVisibility() == VISIBLE)) { + // If action bar is split, adjust buttom insets for it. + insets.bottom += mActionBarHeight; + } + } + + if (changed) { + requestLayout(); + } + + return super.fitSystemWindows(insets); + } + + void pullChildren() { + if (mContent == null) { + mContent = findViewById(com.android.internal.R.id.content); + mActionBarTop = findViewById(com.android.internal.R.id.top_action_bar); + mContainerView = (ActionBarContainer)findViewById( + com.android.internal.R.id.action_bar_container); + mActionView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar); + mActionBarBottom = findViewById(com.android.internal.R.id.split_action_bar); + } + } +} diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 5a7d5195d0d9..93f90f6fa175 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -438,10 +438,9 @@ public class LockPatternUtils { * Calls back SetupFaceLock to delete the temporary gallery file */ public void deleteTempGallery() { - Intent intent = new Intent().setClassName("com.android.facelock", - "com.android.facelock.SetupFaceLock"); + Intent intent = new Intent().setAction("com.android.facelock.DELETE_GALLERY"); intent.putExtra("deleteTempGallery", true); - mContext.startActivity(intent); + mContext.sendBroadcast(intent); } /** @@ -449,10 +448,9 @@ public class LockPatternUtils { */ void deleteGallery() { if(usingBiometricWeak()) { - Intent intent = new Intent().setClassName("com.android.facelock", - "com.android.facelock.SetupFaceLock"); + Intent intent = new Intent().setAction("com.android.facelock.DELETE_GALLERY"); intent.putExtra("deleteGallery", true); - mContext.startActivity(intent); + mContext.sendBroadcast(intent); } } diff --git a/core/java/com/google/android/mms/pdu/PduPersister.java b/core/java/com/google/android/mms/pdu/PduPersister.java index 7c937ed9461a..1a8e80fbfed8 100644 --- a/core/java/com/google/android/mms/pdu/PduPersister.java +++ b/core/java/com/google/android/mms/pdu/PduPersister.java @@ -519,98 +519,99 @@ public class PduPersister { * @throws MmsException Failed to load some fields of a PDU. */ public GenericPdu load(Uri uri) throws MmsException { - PduCacheEntry cacheEntry; - synchronized(PDU_CACHE_INSTANCE) { - if (PDU_CACHE_INSTANCE.isUpdating(uri)) { - if (LOCAL_LOGV) { - Log.v(TAG, "load: " + uri + " blocked by isUpdating()"); - } - try { - PDU_CACHE_INSTANCE.wait(); - } catch (InterruptedException e) { - Log.e(TAG, "load: ", e); - } - cacheEntry = PDU_CACHE_INSTANCE.get(uri); - if (cacheEntry != null) { - return cacheEntry.getPdu(); + GenericPdu pdu = null; + PduCacheEntry cacheEntry = null; + int msgBox = 0; + long threadId = -1; + try { + synchronized(PDU_CACHE_INSTANCE) { + if (PDU_CACHE_INSTANCE.isUpdating(uri)) { + if (LOCAL_LOGV) { + Log.v(TAG, "load: " + uri + " blocked by isUpdating()"); + } + try { + PDU_CACHE_INSTANCE.wait(); + } catch (InterruptedException e) { + Log.e(TAG, "load: ", e); + } + cacheEntry = PDU_CACHE_INSTANCE.get(uri); + if (cacheEntry != null) { + return cacheEntry.getPdu(); + } } + // Tell the cache to indicate to other callers that this item + // is currently being updated. + PDU_CACHE_INSTANCE.setUpdating(uri, true); } - // Tell the cache to indicate to other callers that this item - // is currently being updated. - PDU_CACHE_INSTANCE.setUpdating(uri, true); - } - Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri, - PDU_PROJECTION, null, null, null); - PduHeaders headers = new PduHeaders(); - Set<Entry<Integer, Integer>> set; - long msgId = ContentUris.parseId(uri); - int msgBox; - long threadId; + Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri, + PDU_PROJECTION, null, null, null); + PduHeaders headers = new PduHeaders(); + Set<Entry<Integer, Integer>> set; + long msgId = ContentUris.parseId(uri); - try { - if ((c == null) || (c.getCount() != 1) || !c.moveToFirst()) { - throw new MmsException("Bad uri: " + uri); - } + try { + if ((c == null) || (c.getCount() != 1) || !c.moveToFirst()) { + throw new MmsException("Bad uri: " + uri); + } - msgBox = c.getInt(PDU_COLUMN_MESSAGE_BOX); - threadId = c.getLong(PDU_COLUMN_THREAD_ID); + msgBox = c.getInt(PDU_COLUMN_MESSAGE_BOX); + threadId = c.getLong(PDU_COLUMN_THREAD_ID); - set = ENCODED_STRING_COLUMN_INDEX_MAP.entrySet(); - for (Entry<Integer, Integer> e : set) { - setEncodedStringValueToHeaders( - c, e.getValue(), headers, e.getKey()); - } + set = ENCODED_STRING_COLUMN_INDEX_MAP.entrySet(); + for (Entry<Integer, Integer> e : set) { + setEncodedStringValueToHeaders( + c, e.getValue(), headers, e.getKey()); + } - set = TEXT_STRING_COLUMN_INDEX_MAP.entrySet(); - for (Entry<Integer, Integer> e : set) { - setTextStringToHeaders( - c, e.getValue(), headers, e.getKey()); - } + set = TEXT_STRING_COLUMN_INDEX_MAP.entrySet(); + for (Entry<Integer, Integer> e : set) { + setTextStringToHeaders( + c, e.getValue(), headers, e.getKey()); + } - set = OCTET_COLUMN_INDEX_MAP.entrySet(); - for (Entry<Integer, Integer> e : set) { - setOctetToHeaders( - c, e.getValue(), headers, e.getKey()); - } + set = OCTET_COLUMN_INDEX_MAP.entrySet(); + for (Entry<Integer, Integer> e : set) { + setOctetToHeaders( + c, e.getValue(), headers, e.getKey()); + } - set = LONG_COLUMN_INDEX_MAP.entrySet(); - for (Entry<Integer, Integer> e : set) { - setLongToHeaders( - c, e.getValue(), headers, e.getKey()); - } - } finally { - if (c != null) { - c.close(); + set = LONG_COLUMN_INDEX_MAP.entrySet(); + for (Entry<Integer, Integer> e : set) { + setLongToHeaders( + c, e.getValue(), headers, e.getKey()); + } + } finally { + if (c != null) { + c.close(); + } } - } - - // Check whether 'msgId' has been assigned a valid value. - if (msgId == -1L) { - throw new MmsException("Error! ID of the message: -1."); - } - // Load address information of the MM. - loadAddress(msgId, headers); - - int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE); - PduBody body = new PduBody(); + // Check whether 'msgId' has been assigned a valid value. + if (msgId == -1L) { + throw new MmsException("Error! ID of the message: -1."); + } - // For PDU which type is M_retrieve.conf or Send.req, we should - // load multiparts and put them into the body of the PDU. - if ((msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) - || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) { - PduPart[] parts = loadParts(msgId); - if (parts != null) { - int partsNum = parts.length; - for (int i = 0; i < partsNum; i++) { - body.addPart(parts[i]); + // Load address information of the MM. + loadAddress(msgId, headers); + + int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE); + PduBody body = new PduBody(); + + // For PDU which type is M_retrieve.conf or Send.req, we should + // load multiparts and put them into the body of the PDU. + if ((msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) + || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) { + PduPart[] parts = loadParts(msgId); + if (parts != null) { + int partsNum = parts.length; + for (int i = 0; i < partsNum; i++) { + body.addPart(parts[i]); + } } } - } - GenericPdu pdu = null; - switch (msgType) { + switch (msgType) { case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: pdu = new NotificationInd(headers); break; @@ -657,16 +658,20 @@ public class PduPersister { default: throw new MmsException( "Unrecognized PDU type: " + Integer.toHexString(msgType)); + } + } finally { + synchronized(PDU_CACHE_INSTANCE) { + if (pdu != null) { + assert(PDU_CACHE_INSTANCE.get(uri) == null); + // Update the cache entry with the real info + cacheEntry = new PduCacheEntry(pdu, msgBox, threadId); + PDU_CACHE_INSTANCE.put(uri, cacheEntry); + } + PDU_CACHE_INSTANCE.setUpdating(uri, false); + PDU_CACHE_INSTANCE.notifyAll(); // tell anybody waiting on this entry to go ahead + } } - - synchronized(PDU_CACHE_INSTANCE ) { - assert(PDU_CACHE_INSTANCE.get(uri) == null); - // Update the cache entry with the real info - cacheEntry = new PduCacheEntry(pdu, msgBox, threadId); - PDU_CACHE_INSTANCE.put(uri, cacheEntry); - PDU_CACHE_INSTANCE.notifyAll(); // tell anybody waiting on this entry to go ahead - return pdu; - } + return pdu; } private void persistAddress( diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp index 480c3a660a71..2fe0b9e4625e 100644 --- a/core/jni/android_media_AudioRecord.cpp +++ b/core/jni/android_media_AudioRecord.cpp @@ -254,7 +254,7 @@ native_track_failure: // ---------------------------------------------------------------------------- static int -android_media_AudioRecord_start(JNIEnv *env, jobject thiz) +android_media_AudioRecord_start(JNIEnv *env, jobject thiz, jint event, jint triggerSession) { AudioRecord *lpRecorder = (AudioRecord *)env->GetIntField(thiz, javaAudioRecordFields.nativeRecorderInJavaObj); @@ -263,7 +263,8 @@ android_media_AudioRecord_start(JNIEnv *env, jobject thiz) return AUDIORECORD_ERROR; } - return android_media_translateRecorderErrorCode(lpRecorder->start()); + return android_media_translateRecorderErrorCode( + lpRecorder->start((AudioSystem::sync_event_t)event, triggerSession)); } @@ -508,7 +509,7 @@ static jint android_media_AudioRecord_get_min_buff_size(JNIEnv *env, jobject th // ---------------------------------------------------------------------------- static JNINativeMethod gMethods[] = { // name, signature, funcPtr - {"native_start", "()I", (void *)android_media_AudioRecord_start}, + {"native_start", "(II)I", (void *)android_media_AudioRecord_start}, {"native_stop", "()V", (void *)android_media_AudioRecord_stop}, {"native_setup", "(Ljava/lang/Object;IIIII[I)I", (void *)android_media_AudioRecord_setup}, diff --git a/core/jni/android_media_ToneGenerator.cpp b/core/jni/android_media_ToneGenerator.cpp index 31151a62f13c..da6f1ed4040d 100644 --- a/core/jni/android_media_ToneGenerator.cpp +++ b/core/jni/android_media_ToneGenerator.cpp @@ -65,6 +65,16 @@ static void android_media_ToneGenerator_stopTone(JNIEnv *env, jobject thiz) { lpToneGen->stopTone(); } +static jint android_media_ToneGenerator_getAudioSessionId(JNIEnv *env, jobject thiz) { + ToneGenerator *lpToneGen = (ToneGenerator *)env->GetIntField(thiz, + fields.context); + if (lpToneGen == NULL) { + jniThrowRuntimeException(env, "Method called after release()"); + return 0; + } + return lpToneGen->getSessionId(); +} + static void android_media_ToneGenerator_release(JNIEnv *env, jobject thiz) { ToneGenerator *lpToneGen = (ToneGenerator *)env->GetIntField(thiz, fields.context); @@ -120,6 +130,7 @@ static void android_media_ToneGenerator_native_finalize(JNIEnv *env, static JNINativeMethod gMethods[] = { { "startTone", "(II)Z", (void *)android_media_ToneGenerator_startTone }, { "stopTone", "()V", (void *)android_media_ToneGenerator_stopTone }, + { "getAudioSessionId", "()I", (void *)android_media_ToneGenerator_getAudioSessionId}, { "release", "()V", (void *)android_media_ToneGenerator_release }, { "native_setup", "(II)V", (void *)android_media_ToneGenerator_native_setup }, { "native_finalize", "()V", (void *)android_media_ToneGenerator_native_finalize } diff --git a/core/jni/android_util_Log.cpp b/core/jni/android_util_Log.cpp index 289517162322..536a582aff45 100644 --- a/core/jni/android_util_Log.cpp +++ b/core/jni/android_util_Log.cpp @@ -64,7 +64,7 @@ static jboolean isLoggable(const char* tag, jint level) { char buf[PROPERTY_VALUE_MAX]; if (property_get(key.string(), buf, "") <= 0) { - return false; + buf[0] = '\0'; } int logLevel = toLevel(buf); diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp index b472eef9c9ec..60288140c113 100644 --- a/core/jni/android_view_GLES20Canvas.cpp +++ b/core/jni/android_view_GLES20Canvas.cpp @@ -163,6 +163,21 @@ static jint android_view_GLES20Canvas_callDrawGLFunction(JNIEnv* env, jobject cl return renderer->callDrawGLFunction(functor, dirty); } +static jint android_view_GLES20Canvas_invokeFunctors(JNIEnv* env, + jobject clazz, OpenGLRenderer* renderer, jobject dirty) { + android::uirenderer::Rect bounds; + status_t status = renderer->invokeFunctors(bounds); + if (status != DrawGlInfo::kStatusDone && dirty != NULL) { + env->CallVoidMethod(dirty, gRectClassInfo.set, + int(bounds.left), int(bounds.top), int(bounds.right), int(bounds.bottom)); + } + return status; +} + +// ---------------------------------------------------------------------------- +// Misc +// ---------------------------------------------------------------------------- + static jint android_view_GLES20Canvas_getMaxTextureWidth(JNIEnv* env, jobject clazz) { return Caches::getInstance().maxTextureSize; } @@ -824,6 +839,8 @@ static JNINativeMethod gMethods[] = { { "nGetStencilSize", "()I", (void*) android_view_GLES20Canvas_getStencilSize }, { "nCallDrawGLFunction", "(II)I", (void*) android_view_GLES20Canvas_callDrawGLFunction }, + { "nInvokeFunctors", "(ILandroid/graphics/Rect;)I", + (void*) android_view_GLES20Canvas_invokeFunctors }, { "nSave", "(II)I", (void*) android_view_GLES20Canvas_save }, { "nRestore", "(I)V", (void*) android_view_GLES20Canvas_restore }, @@ -899,9 +916,9 @@ static JNINativeMethod gMethods[] = { { "nDestroyDisplayList", "(I)V", (void*) android_view_GLES20Canvas_destroyDisplayList }, { "nGetDisplayListSize", "(I)I", (void*) android_view_GLES20Canvas_getDisplayListSize }, { "nSetDisplayListName", "(ILjava/lang/String;)V", - (void*) android_view_GLES20Canvas_setDisplayListName }, + (void*) android_view_GLES20Canvas_setDisplayListName }, { "nDrawDisplayList", "(IIIILandroid/graphics/Rect;I)I", - (void*) android_view_GLES20Canvas_drawDisplayList }, + (void*) android_view_GLES20Canvas_drawDisplayList }, { "nCreateDisplayListRenderer", "()I", (void*) android_view_GLES20Canvas_createDisplayListRenderer }, { "nResetDisplayListRenderer", "(I)V", (void*) android_view_GLES20Canvas_resetDisplayListRenderer }, diff --git a/core/jni/android_view_GLES20DisplayList.cpp b/core/jni/android_view_GLES20DisplayList.cpp index 407c1967e4da..60fb6d45d5fa 100644 --- a/core/jni/android_view_GLES20DisplayList.cpp +++ b/core/jni/android_view_GLES20DisplayList.cpp @@ -45,9 +45,14 @@ static void android_view_GLES20DisplayList_setCaching(JNIEnv* env, displayList->setCaching(caching); } -static void android_view_GLES20DisplayList_setApplicationScale(JNIEnv* env, - jobject clazz, DisplayList* displayList, float scale) { - displayList->setApplicationScale(scale); +static void android_view_GLES20DisplayList_setStaticMatrix(JNIEnv* env, + jobject clazz, DisplayList* displayList, SkMatrix* matrix) { + displayList->setStaticMatrix(matrix); +} + +static void android_view_GLES20DisplayList_setAnimationMatrix(JNIEnv* env, + jobject clazz, DisplayList* displayList, SkMatrix* matrix) { + displayList->setAnimationMatrix(matrix); } static void android_view_GLES20DisplayList_setClipChildren(JNIEnv* env, @@ -175,33 +180,32 @@ const char* const kClassPathName = "android/view/GLES20DisplayList"; static JNINativeMethod gMethods[] = { #ifdef USE_OPENGL_RENDERER - { "nSetCaching", "(IZ)V", (void*) android_view_GLES20DisplayList_setCaching }, - { "nSetApplicationScale", "(IF)V", - (void*) android_view_GLES20DisplayList_setApplicationScale }, - { "nSetClipChildren", "(IZ)V", (void*) android_view_GLES20DisplayList_setClipChildren }, - { "nSetAlpha", "(IF)V", (void*) android_view_GLES20DisplayList_setAlpha }, - { "nSetTranslationX", "(IF)V", (void*) android_view_GLES20DisplayList_setTranslationX }, - { "nSetTranslationY", "(IF)V", (void*) android_view_GLES20DisplayList_setTranslationY }, - { "nSetRotation", "(IF)V", (void*) android_view_GLES20DisplayList_setRotation }, - { "nSetRotationX", "(IF)V", (void*) android_view_GLES20DisplayList_setRotationX }, - { "nSetRotationY", "(IF)V", (void*) android_view_GLES20DisplayList_setRotationY }, - { "nSetScaleX", "(IF)V", (void*) android_view_GLES20DisplayList_setScaleX }, - { "nSetScaleY", "(IF)V", (void*) android_view_GLES20DisplayList_setScaleY }, - { "nSetTransformationInfo", "(IFFFFFFFF)V", + { "nSetCaching", "(IZ)V", (void*) android_view_GLES20DisplayList_setCaching }, + { "nSetStaticMatrix", "(II)V", (void*) android_view_GLES20DisplayList_setStaticMatrix }, + { "nSetAnimationMatrix", "(II)V", (void*) android_view_GLES20DisplayList_setAnimationMatrix }, + { "nSetClipChildren", "(IZ)V", (void*) android_view_GLES20DisplayList_setClipChildren }, + { "nSetAlpha", "(IF)V", (void*) android_view_GLES20DisplayList_setAlpha }, + { "nSetTranslationX", "(IF)V", (void*) android_view_GLES20DisplayList_setTranslationX }, + { "nSetTranslationY", "(IF)V", (void*) android_view_GLES20DisplayList_setTranslationY }, + { "nSetRotation", "(IF)V", (void*) android_view_GLES20DisplayList_setRotation }, + { "nSetRotationX", "(IF)V", (void*) android_view_GLES20DisplayList_setRotationX }, + { "nSetRotationY", "(IF)V", (void*) android_view_GLES20DisplayList_setRotationY }, + { "nSetScaleX", "(IF)V", (void*) android_view_GLES20DisplayList_setScaleX }, + { "nSetScaleY", "(IF)V", (void*) android_view_GLES20DisplayList_setScaleY }, + { "nSetTransformationInfo","(IFFFFFFFF)V", (void*) android_view_GLES20DisplayList_setTransformationInfo }, - { "nSetPivotX", "(IF)V", (void*) android_view_GLES20DisplayList_setPivotX }, - { "nSetPivotY", "(IF)V", (void*) android_view_GLES20DisplayList_setPivotY }, - { "nSetCameraDistance", "(IF)V", - (void*) android_view_GLES20DisplayList_setCameraDistance }, - { "nSetLeft", "(II)V", (void*) android_view_GLES20DisplayList_setLeft }, - { "nSetTop", "(II)V", (void*) android_view_GLES20DisplayList_setTop }, - { "nSetRight", "(II)V", (void*) android_view_GLES20DisplayList_setRight }, - { "nSetBottom", "(II)V", (void*) android_view_GLES20DisplayList_setBottom }, - { "nSetLeftTop", "(III)V", (void*) android_view_GLES20DisplayList_setLeftTop }, - { "nSetLeftTopRightBottom", "(IIIII)V", + { "nSetPivotX", "(IF)V", (void*) android_view_GLES20DisplayList_setPivotX }, + { "nSetPivotY", "(IF)V", (void*) android_view_GLES20DisplayList_setPivotY }, + { "nSetCameraDistance", "(IF)V", (void*) android_view_GLES20DisplayList_setCameraDistance }, + { "nSetLeft", "(II)V", (void*) android_view_GLES20DisplayList_setLeft }, + { "nSetTop", "(II)V", (void*) android_view_GLES20DisplayList_setTop }, + { "nSetRight", "(II)V", (void*) android_view_GLES20DisplayList_setRight }, + { "nSetBottom", "(II)V", (void*) android_view_GLES20DisplayList_setBottom }, + { "nSetLeftTop", "(III)V", (void*) android_view_GLES20DisplayList_setLeftTop }, + { "nSetLeftTopRightBottom","(IIIII)V", (void*) android_view_GLES20DisplayList_setLeftTopRightBottom }, - { "nOffsetLeftRight", "(II)V", (void*) android_view_GLES20DisplayList_offsetLeftRight }, - { "nOffsetTopBottom", "(II)V", (void*) android_view_GLES20DisplayList_offsetTopBottom }, + { "nOffsetLeftRight", "(II)V", (void*) android_view_GLES20DisplayList_offsetLeftRight }, + { "nOffsetTopBottom", "(II)V", (void*) android_view_GLES20DisplayList_offsetTopBottom }, #endif }; diff --git a/core/jni/android_view_HardwareRenderer.cpp b/core/jni/android_view_HardwareRenderer.cpp index 4b6216975e52..fa8317001bb3 100644 --- a/core/jni/android_view_HardwareRenderer.cpp +++ b/core/jni/android_view_HardwareRenderer.cpp @@ -20,8 +20,9 @@ #include <nativehelper/JNIHelp.h> #include <android_runtime/AndroidRuntime.h> +#include <EGL/egl_cache.h> + #ifdef USE_OPENGL_RENDERER - #include <EGL/egl_cache.h> EGLAPI void EGLAPIENTRY eglBeginFrame(EGLDisplay dpy, EGLSurface surface); #endif @@ -99,10 +100,26 @@ static void android_view_HardwareRenderer_disableVsync(JNIEnv* env, jobject claz // Tracing and debugging // ---------------------------------------------------------------------------- -static void android_view_HardwareRenderer_beginFrame(JNIEnv* env, jobject clazz) { - EGLDisplay dpy = eglGetCurrentDisplay(); - EGLSurface surf = eglGetCurrentSurface(EGL_DRAW); - eglBeginFrame(dpy, surf); +static void android_view_HardwareRenderer_beginFrame(JNIEnv* env, jobject clazz, + jintArray size) { + + EGLDisplay display = eglGetCurrentDisplay(); + EGLSurface surface = eglGetCurrentSurface(EGL_DRAW); + + if (size) { + EGLint value; + jint* storage = env->GetIntArrayElements(size, NULL); + + eglQuerySurface(display, surface, EGL_WIDTH, &value); + storage[0] = value; + + eglQuerySurface(display, surface, EGL_HEIGHT, &value); + storage[1] = value; + + env->ReleaseIntArrayElements(size, storage, 0); + } + + eglBeginFrame(display, surface); } #endif // USE_OPENGL_RENDERER @@ -127,15 +144,11 @@ const char* const kClassPathName = "android/view/HardwareRenderer"; static JNINativeMethod gMethods[] = { #ifdef USE_OPENGL_RENDERER - { "nIsBackBufferPreserved", "()Z", - (void*) android_view_HardwareRenderer_isBackBufferPreserved }, - { "nPreserveBackBuffer", "()Z", - (void*) android_view_HardwareRenderer_preserveBackBuffer }, - { "nDisableVsync", "()V", - (void*) android_view_HardwareRenderer_disableVsync }, - - { "nBeginFrame", "()V", - (void*) android_view_HardwareRenderer_beginFrame }, + { "nIsBackBufferPreserved", "()Z", (void*) android_view_HardwareRenderer_isBackBufferPreserved }, + { "nPreserveBackBuffer", "()Z", (void*) android_view_HardwareRenderer_preserveBackBuffer }, + { "nDisableVsync", "()V", (void*) android_view_HardwareRenderer_disableVsync }, + + { "nBeginFrame", "([I)V", (void*) android_view_HardwareRenderer_beginFrame }, #endif { "nSetupShadersDiskCache", "(Ljava/lang/String;)V", diff --git a/core/res/res/layout-xlarge/screen_action_bar_overlay.xml b/core/res/res/layout-xlarge/screen_action_bar_overlay.xml index 89f0d89d01aa..f2a1ea1fcfcb 100644 --- a/core/res/res/layout-xlarge/screen_action_bar_overlay.xml +++ b/core/res/res/layout-xlarge/screen_action_bar_overlay.xml @@ -19,31 +19,39 @@ This is an optimized layout for a screen with the Action Bar enabled overlaying application content. --> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:fitsSystemWindows="true"> +<com.android.internal.widget.ActionBarOverlayLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/action_bar_overlay_layout" + android:layout_width="match_parent" + android:layout_height="match_parent"> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent" /> - <com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="?android:attr/actionBarStyle" - android:gravity="top"> - <com.android.internal.widget.ActionBarView - android:id="@+id/action_bar" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="?android:attr/actionBarStyle" /> - <com.android.internal.widget.ActionBarContextView - android:id="@+id/action_context_bar" + <LinearLayout android:id="@+id/top_action_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="top"> + <com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:visibility="gone" - style="?android:attr/actionModeStyle" /> - </com.android.internal.widget.ActionBarContainer> - <ImageView android:src="?android:attr/windowContentOverlay" - android:scaleType="fitXY" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_below="@id/action_bar_container" /> -</RelativeLayout> + android:layout_alignParentTop="true" + style="?android:attr/actionBarStyle" + android:gravity="top"> + <com.android.internal.widget.ActionBarView + android:id="@+id/action_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="?android:attr/actionBarStyle" /> + <com.android.internal.widget.ActionBarContextView + android:id="@+id/action_context_bar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" + style="?android:attr/actionModeStyle" /> + </com.android.internal.widget.ActionBarContainer> + <ImageView android:src="?android:attr/windowContentOverlay" + android:scaleType="fitXY" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + </LinearLayout> +</com.android.internal.widget.ActionBarOverlayLayout> diff --git a/core/res/res/layout/notification_action.xml b/core/res/res/layout/notification_action.xml new file mode 100644 index 000000000000..54fde70b29d1 --- /dev/null +++ b/core/res/res/layout/notification_action.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<Button xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/action0" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@android:style/Widget.Holo.Button.Small" + android:gravity="left" + />
\ No newline at end of file diff --git a/core/res/res/layout/notification_template_base.xml b/core/res/res/layout/notification_template_base.xml new file mode 100644 index 000000000000..5b0646070c05 --- /dev/null +++ b/core/res/res/layout/notification_template_base.xml @@ -0,0 +1,137 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/status_bar_latest_event_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + > + <ImageView android:id="@+id/icon" + android:layout_width="@dimen/notification_large_icon_width" + android:layout_height="@dimen/notification_large_icon_height" + android:background="@android:drawable/notify_panel_notification_icon_bg_tile" + android:scaleType="center" + /> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginLeft="@dimen/notification_large_icon_width" + android:minHeight="@dimen/notification_large_icon_height" + android:orientation="vertical" + android:paddingLeft="12dp" + android:paddingRight="12dp" + android:paddingTop="4dp" + android:paddingBottom="4dp" + > + <LinearLayout + android:id="@+id/line1" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <TextView android:id="@+id/title" + android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="true" + android:ellipsize="marquee" + android:fadingEdge="horizontal" + android:layout_weight="1" + /> + <DateTimeView android:id="@+id/time" + android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_weight="0" + android:singleLine="true" + android:gravity="center" + android:paddingLeft="8dp" + /> + </LinearLayout> + <TextView android:id="@+id/text2" + android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Line2" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="-2dp" + android:layout_marginBottom="-2dp" + android:singleLine="true" + android:fadingEdge="horizontal" + android:ellipsize="marquee" + android:visibility="gone" + /> + <TextView android:id="@+id/big_text" + android:textAppearance="@style/TextAppearance.StatusBar.EventContent" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:singleLine="false" + android:visibility="gone" + /> + <LinearLayout + android:id="@+id/line3" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + > + <TextView android:id="@+id/text" + android:textAppearance="@style/TextAppearance.StatusBar.EventContent" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_gravity="center" + android:singleLine="true" + android:ellipsize="marquee" + android:fadingEdge="horizontal" + /> + <TextView android:id="@+id/info" + android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Info" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_weight="0" + android:singleLine="true" + android:gravity="center" + android:paddingLeft="8dp" + /> + <ImageView android:id="@+id/right_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_weight="0" + android:scaleType="center" + android:paddingLeft="8dp" + android:visibility="gone" + android:drawableAlpha="180" + /> + </LinearLayout> + <ProgressBar + android:id="@android:id/progress" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" + style="?android:attr/progressBarStyleHorizontal" + /> + <LinearLayout + android:id="@+id/actions" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" + > + <!-- actions will be added here --> + </LinearLayout> + </LinearLayout> +</FrameLayout> diff --git a/core/res/res/layout/notification_template_big_picture.xml b/core/res/res/layout/notification_template_big_picture.xml index 6eb934e50cd9..8be84bd4e4ec 100644 --- a/core/res/res/layout/notification_template_big_picture.xml +++ b/core/res/res/layout/notification_template_big_picture.xml @@ -1,3 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/status_bar_latest_event_content" android:layout_width="match_parent" @@ -9,9 +25,9 @@ android:layout_height="192dp" android:scaleType="centerCrop" /> - <include layout="@layout/status_bar_latest_event_content" + <include layout="@layout/notification_template_base" android:layout_width="match_parent" - android:layout_height="@dimen/notification_large_icon_height" + android:layout_height="wrap_content" android:layout_marginTop="192dp" /> </FrameLayout>
\ No newline at end of file diff --git a/core/res/res/layout/screen_action_bar_overlay.xml b/core/res/res/layout/screen_action_bar_overlay.xml index 2a8c7c381857..20a7db1875af 100644 --- a/core/res/res/layout/screen_action_bar_overlay.xml +++ b/core/res/res/layout/screen_action_bar_overlay.xml @@ -19,14 +19,16 @@ This is an optimized layout for a screen with the Action Bar enabled overlaying application content. --> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" +<com.android.internal.widget.ActionBarOverlayLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/action_bar_overlay_layout" android:layout_width="match_parent" - android:layout_height="match_parent" - android:fitsSystemWindows="true"> + android:layout_height="match_parent"> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent" /> - <LinearLayout android:layout_width="match_parent" + <LinearLayout android:id="@+id/top_action_bar" + android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="top"> <com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container" @@ -50,8 +52,7 @@ the Action Bar enabled overlaying application content. <ImageView android:src="?android:attr/windowContentOverlay" android:scaleType="fitXY" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_below="@id/action_bar_container" /> + android:layout_height="wrap_content" /> </LinearLayout> <com.android.internal.widget.ActionBarContainer android:id="@+id/split_action_bar" android:layout_width="match_parent" @@ -60,4 +61,4 @@ the Action Bar enabled overlaying application content. style="?android:attr/actionBarSplitStyle" android:visibility="gone" android:gravity="center"/> -</FrameLayout> +</com.android.internal.widget.ActionBarOverlayLayout> diff --git a/core/res/res/layout/status_bar_latest_event_content.xml b/core/res/res/layout/status_bar_latest_event_content.xml index 57c149f10ea4..b3db01a008f9 100644 --- a/core/res/res/layout/status_bar_latest_event_content.xml +++ b/core/res/res/layout/status_bar_latest_event_content.xml @@ -1,17 +1,29 @@ -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<!-- Nobody should be using this file directly. If you do, you will get a + purple background. Have fun with that. --> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/status_bar_latest_event_content" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="#FFFF00FF" > - <ImageView android:id="@+id/icon" - android:layout_width="@dimen/notification_large_icon_width" - android:layout_height="@dimen/notification_large_icon_height" - android:background="@android:drawable/notify_panel_notification_icon_bg_tile" - android:scaleType="center" - /> - <include layout="@layout/status_bar_latest_event_content_large_icon" + <include layout="@layout/notification_template_base" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="center" /> -</LinearLayout> +</FrameLayout> diff --git a/core/res/res/layout/status_bar_latest_event_content_large_icon.xml b/core/res/res/layout/status_bar_latest_event_content_large_icon.xml deleted file mode 100644 index 5f38e6a5c4f0..000000000000 --- a/core/res/res/layout/status_bar_latest_event_content_large_icon.xml +++ /dev/null @@ -1,100 +0,0 @@ -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/status_bar_latest_event_content_large_icon" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:orientation="vertical" - android:paddingLeft="12dp" - android:paddingRight="12dp" - android:paddingTop="4dp" - android:paddingBottom="4dp" - > - <LinearLayout - android:id="@+id/line1" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - > - <TextView android:id="@+id/title" - android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Title" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:singleLine="true" - android:ellipsize="marquee" - android:fadingEdge="horizontal" - android:layout_weight="1" - /> - <DateTimeView android:id="@+id/time" - android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Time" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_weight="0" - android:singleLine="true" - android:gravity="center" - android:paddingLeft="8dp" - /> - </LinearLayout> - <TextView android:id="@+id/text2" - android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Line2" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="-2dp" - android:layout_marginBottom="-2dp" - android:singleLine="true" - android:fadingEdge="horizontal" - android:ellipsize="marquee" - android:visibility="gone" - /> - <TextView android:id="@+id/big_text" - android:textAppearance="@style/TextAppearance.StatusBar.EventContent" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:singleLine="false" - android:visibility="gone" - /> - <LinearLayout - android:id="@+id/line3" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - > - <TextView android:id="@+id/text" - android:textAppearance="@style/TextAppearance.StatusBar.EventContent" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_gravity="center" - android:singleLine="true" - android:ellipsize="marquee" - android:fadingEdge="horizontal" - /> - <TextView android:id="@+id/info" - android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Info" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_weight="0" - android:singleLine="true" - android:gravity="center" - android:paddingLeft="8dp" - /> - <ImageView android:id="@+id/right_icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_weight="0" - android:scaleType="center" - android:paddingLeft="8dp" - android:visibility="gone" - android:drawableAlpha="180" - /> - </LinearLayout> - <ProgressBar - android:id="@android:id/progress" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:visibility="gone" - style="?android:attr/progressBarStyleHorizontal" - /> -</LinearLayout> diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml index f020f5db57e4..203c33570631 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Voer \'n PUK van 8 syfers of langer in."</string> <string name="needPuk" msgid="919668385956251611">"Jou SIM-kaart is PUK-gesluit. Voer die PUK-kode in om dit te ontsluit."</string> <string name="needPuk2" msgid="4526033371987193070">"Sleutel PUK2 in om SIM-kaart oop te sluit."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Inkomender beller-ID"</string> <string name="ClirMmi" msgid="7784673673446833091">"Uitgaande beller-ID"</string> <string name="CfMmi" msgid="5123218989141573515">"Oproepaanstuur"</string> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index 0481a67ebf29..ab937c51ce3b 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"8 ወይም ከዛ በላይ የሆኑ ቁጥሮችንPUK ተይብ።"</string> <string name="needPuk" msgid="919668385956251611">"SIM ካርድዎ PUK-የተቆለፈ ነው።የPUK ኮዱን በመተየብ ይክፈቱት።"</string> <string name="needPuk2" msgid="4526033371987193070">" SIM ለመክፈት PUK2 ተይብ።"</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"የገቢ ደዋይID"</string> <string name="ClirMmi" msgid="7784673673446833091">"የወጪ ጥሪID"</string> <string name="CfMmi" msgid="5123218989141573515">"ጥሪ ማስተላለፍ"</string> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index a156e64ce372..aca8f4754396 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"اكتب رمز PUK مكونًا من 8 أرقام أو أكثر."</string> <string name="needPuk" msgid="919668385956251611">"بطاقة SIM مؤمّنة بكود PUK. اكتب كود PUK لإلغاء تأمينها."</string> <string name="needPuk2" msgid="4526033371987193070">"اكتب PUK2 لإلغاء تأمين بطاقة SIM."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"معرف المتصل الوارد"</string> <string name="ClirMmi" msgid="7784673673446833091">"معرف المتصل الصادر"</string> <string name="CfMmi" msgid="5123218989141573515">"إعادة توجيه الاتصال"</string> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index 017876457521..4d304f12fd22 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Увядзіце PUK з 8 лічбаў ці больш."</string> <string name="needPuk" msgid="919668385956251611">"Ваша SIM-карта заблакавана PUK-кодам. Увядзіце PUK, каб разблакаваць карту."</string> <string name="needPuk2" msgid="4526033371987193070">"Увядзіце PUK2 для разблакавання SIM-карты."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Ідэнтыфікатар АВН"</string> <string name="ClirMmi" msgid="7784673673446833091">"Ідэнтыфікатар АВН"</string> <string name="CfMmi" msgid="5123218989141573515">"Пераадрасацыя выкліку"</string> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 479e118d462d..b057cef4b415 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Въведете PUK код с поне осем цифри."</string> <string name="needPuk" msgid="919668385956251611">"SIM картата ви е заключена с PUK. Въведете PUK кода, за да я отключите."</string> <string name="needPuk2" msgid="4526033371987193070">"Въведете PUK2, за да отблокирате SIM картата."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Идентификация на вх. обаждания"</string> <string name="ClirMmi" msgid="7784673673446833091">"Идентификация на изходящите повиквания"</string> <string name="CfMmi" msgid="5123218989141573515">"Пренасочване на повиквания"</string> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 64d0f8780cca..61c3cf09926c 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Introdueix un PUK compost com a mínim de 8 nombres."</string> <string name="needPuk" msgid="919668385956251611">"La targeta SIM està bloquejada pel PUK. Escriviu el codi PUK per desbloquejar-la."</string> <string name="needPuk2" msgid="4526033371987193070">"Escriviu el PUK2 per desbloquejar la targeta SIM."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Identificació de trucada entrant"</string> <string name="ClirMmi" msgid="7784673673446833091">"Identificació de trucada de sortida"</string> <string name="CfMmi" msgid="5123218989141573515">"Desviació de trucades"</string> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index d2a4cc1aa2a7..ec640f79d10f 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Zadejte osmimístný nebo delší kód PUK."</string> <string name="needPuk" msgid="919668385956251611">"Karta SIM je blokována pomocí kódu PUK. Odblokujete ji zadáním kódu PUK."</string> <string name="needPuk2" msgid="4526033371987193070">"Chcete-li odblokovat kartu SIM, zadejte kód PUK2."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Příchozí identifikace volajícího"</string> <string name="ClirMmi" msgid="7784673673446833091">"Odchozí identifikace volajícího"</string> <string name="CfMmi" msgid="5123218989141573515">"Přesměrování hovorů"</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index b139e5e577df..d72364b5d21a 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Angiv en PUK-kode på 8 eller flere cifre."</string> <string name="needPuk" msgid="919668385956251611">"Dit SIM-kort er låst med PUK-koden. Indtast PUK-koden for at låse den op."</string> <string name="needPuk2" msgid="4526033371987193070">"Indtast PUK2-koden for at låse op for SIM-kortet."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Indgående opkalds-id"</string> <string name="ClirMmi" msgid="7784673673446833091">"Udgående opkalds-id"</string> <string name="CfMmi" msgid="5123218989141573515">"Viderestilling af opkald"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index baf3e40e8a82..defd8506f075 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Geben Sie eine mindestens achtstellige PUK ein."</string> <string name="needPuk" msgid="919668385956251611">"Ihre SIM-Karte ist mit einem PUK gesperrt. Geben Sie zum Entsperren den PUK-Code ein."</string> <string name="needPuk2" msgid="4526033371987193070">"Geben Sie zum Entsperren der SIM-Karte den PUK2 ein."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Anrufer-ID für eingehenden Anruf"</string> <string name="ClirMmi" msgid="7784673673446833091">"Anrufer-ID für ausgehenden Anruf"</string> <string name="CfMmi" msgid="5123218989141573515">"Rufweiterleitung"</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index ad840e3523e3..71547ce15cfa 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Πληκτρολογήστε έναν κωδικό PUK με 8 αριθμούς ή περισσότερους."</string> <string name="needPuk" msgid="919668385956251611">"Η κάρτα SIM έχει κλειδωθεί με κωδικό PUK. Πληκτρολογήστε τον κωδικό PUK για να την ξεκλειδώσετε."</string> <string name="needPuk2" msgid="4526033371987193070">"Πληκτρολογήστε τον κωδικό PUK2 για την κατάργηση αποκλεισμού της κάρτας SIM."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Εισερχόμενη αναγνώριση κλήσης"</string> <string name="ClirMmi" msgid="7784673673446833091">"Εξερχόμενη αναγνώριση κλήσης"</string> <string name="CfMmi" msgid="5123218989141573515">"Προώθηση κλήσεων"</string> diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml index de92665f3d9b..b070e02bfae1 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Type a PUK that is 8 numbers or longer."</string> <string name="needPuk" msgid="919668385956251611">"Your SIM card is PUK-locked. Type the PUK code to unlock it."</string> <string name="needPuk2" msgid="4526033371987193070">"Type PUK2 to unblock SIM card."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Incoming Caller ID"</string> <string name="ClirMmi" msgid="7784673673446833091">"Outgoing Caller ID"</string> <string name="CfMmi" msgid="5123218989141573515">"Call forwarding"</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 8761ca9e996e..8de6b8c0f122 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Ingresa un código PUK de ocho números o más."</string> <string name="needPuk" msgid="919668385956251611">"Tu tarjeta SIM está bloqueada con PUK. Escribe el código PUK para desbloquearla."</string> <string name="needPuk2" msgid="4526033371987193070">"Escribir PUK2 para desbloquear la tarjeta SIM."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Identificador de llamadas entrantes"</string> <string name="ClirMmi" msgid="7784673673446833091">"Identificador de llamadas salientes"</string> <string name="CfMmi" msgid="5123218989141573515">"Desvío de llamadas"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 9ede6a06517d..b76053f2cbfb 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Escribe un código PUK de ocho caracteres o más."</string> <string name="needPuk" msgid="919668385956251611">"La tarjeta SIM está bloqueada con el código PUK. Introduce el código PUK para desbloquearla."</string> <string name="needPuk2" msgid="4526033371987193070">"Introduce el código PUK2 para desbloquear la tarjeta SIM."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"ID de emisor de llamada entrante"</string> <string name="ClirMmi" msgid="7784673673446833091">"ID de emisor de llamada saliente"</string> <string name="CfMmi" msgid="5123218989141573515">"Desvío de llamada"</string> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index b68944e29434..38c92518e0b6 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Sisestage 8- või enamanumbriline PUK-kood."</string> <string name="needPuk" msgid="919668385956251611">"SIM-kaart on PUK-lukustatud. Avamiseks sisestage PUK-kood."</string> <string name="needPuk2" msgid="4526033371987193070">"Sisestage SIM-kaardi blokeeringu tühistamiseks PUK2-kood."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Sissetuleva kõne helistaja ID"</string> <string name="ClirMmi" msgid="7784673673446833091">"Väljuva kõne helistaja ID"</string> <string name="CfMmi" msgid="5123218989141573515">"Kõnede suunamine"</string> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index 7ee47e7cc54a..5a50a8f0796e 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"یک PUK با 8 رقم یا بیشتر تایپ کنید."</string> <string name="needPuk" msgid="919668385956251611">"سیم کارت شما با PUK قفل شده است. کد PUK را برای بازگشایی آن بنویسید."</string> <string name="needPuk2" msgid="4526033371987193070">"PUK2 را برای بازگشایی قفل سیم کارت بنویسید."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"شناسه تماس گیرنده ورودی"</string> <string name="ClirMmi" msgid="7784673673446833091">"شناسه تماس گیرنده خروجی"</string> <string name="CfMmi" msgid="5123218989141573515">"هدایت تماس"</string> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index b1009a551de4..6d849dd20936 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Kirjoita vähintään 8 numeron pituinen PUK-koodi."</string> <string name="needPuk" msgid="919668385956251611">"SIM-korttisi on PUK-lukittu. Poista lukitus antamalla PUK-koodi."</string> <string name="needPuk2" msgid="4526033371987193070">"Pura SIM-kortin esto antamalla PUK2-koodi."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Soittajan tunnus"</string> <string name="ClirMmi" msgid="7784673673446833091">"Soittajan tunnus"</string> <string name="CfMmi" msgid="5123218989141573515">"Soitonsiirto"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index d044a706af68..548d66ba62df 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Saisissez un code PUK comportant au moins huit chiffres."</string> <string name="needPuk" msgid="919668385956251611">"Votre carte SIM est verrouillée par clé PUK. Saisissez la clé PUK pour la déverrouiller."</string> <string name="needPuk2" msgid="4526033371987193070">"Saisissez la clé PUK2 pour débloquer la carte SIM."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Numéro de l\'appelant (entrant)"</string> <string name="ClirMmi" msgid="7784673673446833091">"Numéro de l\'appelant (sortant)"</string> <string name="CfMmi" msgid="5123218989141573515">"Transfert d\'appel"</string> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index bd8863fe4d77..be853781db82 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"ऐसा PUK लिखें जो 8 अंकों या अधिक का हो."</string> <string name="needPuk" msgid="919668385956251611">"आपका सिम कार्ड PUK लॉक किया गया है. इसे अनलॉक करने के लिए PUK कोड लिखें."</string> <string name="needPuk2" msgid="4526033371987193070">"सिम कार्ड अनब्लॉक करने के लिए PUK2 लिखें."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"इनकमिंग कॉलर ID"</string> <string name="ClirMmi" msgid="7784673673446833091">"आउटगोइंग कॉलर ID"</string> <string name="CfMmi" msgid="5123218989141573515">"कॉल अग्रेषण"</string> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 749012be7606..a30a5e0a02db 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Upišite PUK koji se sastoji od barem 8 brojeva."</string> <string name="needPuk" msgid="919668385956251611">"Vaša je SIM kartica zaključana PUK-om. Unesite PUK kôd da biste je otključali."</string> <string name="needPuk2" msgid="4526033371987193070">"Unesite PUK2 da biste odblokirali SIM karticu."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"ID dolaznog pozivatelja"</string> <string name="ClirMmi" msgid="7784673673446833091">"ID izlaznog pozivatelja"</string> <string name="CfMmi" msgid="5123218989141573515">"Preusmjeravanje poziva"</string> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index 24a70d751ea9..f6fd651c6693 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"8 számjegyű vagy hosszabb PUK kódot írjon be."</string> <string name="needPuk" msgid="919668385956251611">"A SIM-kártya le van zárva a PUK-kóddal. A feloldáshoz adja meg a PUK-kódot."</string> <string name="needPuk2" msgid="4526033371987193070">"A SIM-kártya feloldásához adja meg a PUK2-kódot."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Beérkező hívóazonosító"</string> <string name="ClirMmi" msgid="7784673673446833091">"Kimenő hívóazonosító"</string> <string name="CfMmi" msgid="5123218989141573515">"Hívásátirányítás"</string> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 55a298e21b17..4a1420834399 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Ketik PUK yang terdiri dari 8 angka atau lebih."</string> <string name="needPuk" msgid="919668385956251611">"Kartu SIM Anda dikunci PUK. Ketikkan kode PUK untuk membukanya."</string> <string name="needPuk2" msgid="4526033371987193070">"Ketikkan PUK2 untuk membuka kartu SIM"</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Nomor Penelepon Masuk"</string> <string name="ClirMmi" msgid="7784673673446833091">"Nomor Penelepon Keluar"</string> <string name="CfMmi" msgid="5123218989141573515">"Penerusan panggilan"</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index 75ed4e8d300c..c682a85cb101 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Digita un PUK formato da almeno 8 numeri."</string> <string name="needPuk" msgid="919668385956251611">"La SIM è bloccata tramite PUK. Digita il codice PUK per sbloccarla."</string> <string name="needPuk2" msgid="4526033371987193070">"Digita il PUK2 per sbloccare la SIM."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"ID chiamante in entrata"</string> <string name="ClirMmi" msgid="7784673673446833091">"ID chiamante in uscita"</string> <string name="CfMmi" msgid="5123218989141573515">"Deviazione chiamate"</string> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index f51f2329b7b3..9ebc58d07b4a 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"הקלד PUK באורך 8 מספרים או יותר."</string> <string name="needPuk" msgid="919668385956251611">"כרטיס ה-SIM נעול באמצעות PUK. הקלד את קוד PUK כדי לבטל את נעילתו."</string> <string name="needPuk2" msgid="4526033371987193070">"הקלד PUK2 כדי לבטל את חסימת כרטיס ה-SIM."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"זיהוי מתקשר של שיחה נכנסת"</string> <string name="ClirMmi" msgid="7784673673446833091">"זיהוי מתקשר בשיחה יוצאת"</string> <string name="CfMmi" msgid="5123218989141573515">"העברת שיחות"</string> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 549c1e208310..9f70bb38e403 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"PUKは8桁以上で入力してください。"</string> <string name="needPuk" msgid="919668385956251611">"SIMカードはPUKでロックされています。ロックを解除するにはPUKコードを入力してください。"</string> <string name="needPuk2" msgid="4526033371987193070">"SIMカードのロック解除のためPUK2を入力します。"</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"着信時の発信者番号"</string> <string name="ClirMmi" msgid="7784673673446833091">"発信者番号"</string> <string name="CfMmi" msgid="5123218989141573515">"着信転送"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index fc8e7b1e7cb0..0057a5bb6245 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"8자리 이상의 숫자 PUK를 입력합니다."</string> <string name="needPuk" msgid="919668385956251611">"SIM 카드의 PUK가 잠겨 있습니다. 잠금해제하려면 PUK 코드를 입력하세요."</string> <string name="needPuk2" msgid="4526033371987193070">"SIM 카드 잠금을 해제하려면 PUK2를 입력하세요."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"발신자 번호"</string> <string name="ClirMmi" msgid="7784673673446833091">"내 발신 번호"</string> <string name="CfMmi" msgid="5123218989141573515">"착신전환"</string> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index 5e9d3f4930cb..8aeb17073ed8 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Įveskite 8 skaitmenų ar ilgesnį PUK kodą."</string> <string name="needPuk" msgid="919668385956251611">"Jūsų SIM kortelė yra užrakinta PUK kodu. Jei norite ją atrakinti, įveskite PUK kodą."</string> <string name="needPuk2" msgid="4526033371987193070">"Įveskite PUK2 kodą, kad panaikintumėte SIM kortelės blokavimą."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Įeinančio skambintojo ID"</string> <string name="ClirMmi" msgid="7784673673446833091">"Išeinančio skambintojo ID"</string> <string name="CfMmi" msgid="5123218989141573515">"Skambučio peradresavimas"</string> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index a568c3b5e4b5..f434e09bb3c2 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Ierakstiet PUK kodu, kas sastāv no 8 vai vairāk cipariem."</string> <string name="needPuk" msgid="919668385956251611">"SIM karte ir bloķēta ar PUK kodu. Ierakstiet PUK kodu, lai to atbloķētu."</string> <string name="needPuk2" msgid="4526033371987193070">"Ierakstiet PUK2 kodu, lai atbloķētu SIM karti."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Ienākošā zvana zvanītāja ID"</string> <string name="ClirMmi" msgid="7784673673446833091">"Izejošā zvana zvanītāja ID"</string> <string name="CfMmi" msgid="5123218989141573515">"Zvanu pāradresācija"</string> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index 363832d93dda..a2dd90c24641 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Taipkan PUK yang mempunyai 8 nombor atau lebih panjang."</string> <string name="needPuk" msgid="919668385956251611">"Kad SIM anda dikunci PUK. Taipkan kod PUK untuk membuka kuncinya."</string> <string name="needPuk2" msgid="4526033371987193070">"Taipkan PUK2 untuk menyahsekat kad SIM."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"ID Pemanggil Masuk"</string> <string name="ClirMmi" msgid="7784673673446833091">"ID Pemanggil Keluar"</string> <string name="CfMmi" msgid="5123218989141573515">"Pemajuan panggilan"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index 384fdaca9d2c..b3ea39ff3d88 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Skriv inn en PUK-kode på åtte tall eller mer."</string> <string name="needPuk" msgid="919668385956251611">"SIM-kortet ditt er PUK-låst. Skriv inn PUK-koden for å låse det opp."</string> <string name="needPuk2" msgid="4526033371987193070">"Skriv inn PUK2 for å låse opp SIM-kortet."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Inngående nummervisning"</string> <string name="ClirMmi" msgid="7784673673446833091">"Utgående nummervisning"</string> <string name="CfMmi" msgid="5123218989141573515">"Viderekobling"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 030222d01da9..5698f033b54d 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Typ een PUK-code die 8 cijfers of langer is."</string> <string name="needPuk" msgid="919668385956251611">"Uw SIM-kaart is vergrendeld met de PUK-code. Typ de PUK-code om te ontgrendelen."</string> <string name="needPuk2" msgid="4526033371987193070">"Voer de PUK2-code in om de SIM-kaart te ontgrendelen."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Inkomende beller-id"</string> <string name="ClirMmi" msgid="7784673673446833091">"Uitgaande beller-id"</string> <string name="CfMmi" msgid="5123218989141573515">"Oproep doorschakelen"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 9e4cbf3bd2eb..da2f78d8ecb9 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Wpisz kod PUK składający się z co najmniej 8 cyfr."</string> <string name="needPuk" msgid="919668385956251611">"Karta SIM jest zablokowana kodem PUK. Wprowadź kod PUK, aby odblokować kartę."</string> <string name="needPuk2" msgid="4526033371987193070">"Wprowadź kod PUK2, aby odblokować kartę SIM."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Identyfikator rozmówcy przy połączeniach przychodzących"</string> <string name="ClirMmi" msgid="7784673673446833091">"Identyfikator rozmówcy przy połączeniach wychodzących"</string> <string name="CfMmi" msgid="5123218989141573515">"Przekazywanie połączeń"</string> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index abe1454af0bb..ec434917faf8 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Introduza um PUK que tenha 8 ou mais algarismos."</string> <string name="needPuk" msgid="919668385956251611">"O seu cartão SIM está bloqueado com PUK. Introduza o código PUK para desbloqueá-lo."</string> <string name="needPuk2" msgid="4526033371987193070">"Introduza o PUK2 para desbloquear o cartão SIM."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"ID do Autor da Chamada"</string> <string name="ClirMmi" msgid="7784673673446833091">"ID do autor da chamada efetuada"</string> <string name="CfMmi" msgid="5123218989141573515">"Encaminhamento de chamadas"</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 3ae566cf4191..53e2a96077c5 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Digite um PUK com oito números ou mais."</string> <string name="needPuk" msgid="919668385956251611">"O seu cartão SIM está bloqueado por um PUK. Digite o código PUK para desbloqueá-lo."</string> <string name="needPuk2" msgid="4526033371987193070">"Digite o PUK2 para desbloquear o cartão SIM."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"ID do chamador de entrada"</string> <string name="ClirMmi" msgid="7784673673446833091">"ID do chamador de saída"</string> <string name="CfMmi" msgid="5123218989141573515">"Encaminhamento de chamada"</string> diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml index fc6a51022b4b..5f0561b9b122 100644 --- a/core/res/res/values-rm/strings.xml +++ b/core/res/res/values-rm/strings.xml @@ -57,6 +57,10 @@ <skip /> <string name="needPuk" msgid="919668385956251611">"Vossa carta SIM è bloccada cun in PUK. Endatai il PUK per debloccar ella."</string> <string name="needPuk2" msgid="4526033371987193070">"Endatai il PUK2 per debloccar la carta SIM."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Numer dal telefonader (entrant)"</string> <string name="ClirMmi" msgid="7784673673446833091">"ID dal telefonader per cloms sortints"</string> <string name="CfMmi" msgid="5123218989141573515">"Renviament da clom"</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index d443d1df961a..11199b07eb3f 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Introduceţi un cod PUK care să aibă 8 cifre sau mai mult."</string> <string name="needPuk" msgid="919668385956251611">"Cardul SIM este blocat cu codul PUK. Introduceţi codul PUK pentru a-l debloca."</string> <string name="needPuk2" msgid="4526033371987193070">"Introduceţi codul PUK2 pentru a debloca cardul SIM."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"ID apelant de primire"</string> <string name="ClirMmi" msgid="7784673673446833091">"ID apelant"</string> <string name="CfMmi" msgid="5123218989141573515">"Redirecţionarea apelurilor"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 5ed4c2e61f4f..d4adfb46adf5 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Введите PUK-код из 8 или более цифр."</string> <string name="needPuk" msgid="919668385956251611">"SIM-карта заблокирована с помощью кода PUK. Для разблокировки введите код PUK."</string> <string name="needPuk2" msgid="4526033371987193070">"Для разблокировки SIM-карты введите PUK2."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Идентификация вызывающего абонента"</string> <string name="ClirMmi" msgid="7784673673446833091">"Идентификация звонящего абонента"</string> <string name="CfMmi" msgid="5123218989141573515">"Переадресация вызова"</string> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index 2e97b82b62e7..bad739f952cd 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Zadajte kód PUK, ktorý má 8 alebo viac čísel."</string> <string name="needPuk" msgid="919668385956251611">"Karta SIM je uzamknutá pomocou kódu PUK. Odomknite ju zadaním kódu PUK."</string> <string name="needPuk2" msgid="4526033371987193070">"Ak chcete odblokovať kartu SIM, zadajte kód PUK2."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Prichádzajúca identifikácia volajúceho"</string> <string name="ClirMmi" msgid="7784673673446833091">"Odchádzajúca identifikácia volajúceho"</string> <string name="CfMmi" msgid="5123218989141573515">"Presmerovanie hovorov"</string> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index f66cabb855e8..767305ca2c2f 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Vnesite 8- ali več mestni PUK."</string> <string name="needPuk" msgid="919668385956251611">"Kartica SIM je zaklenjena s kodo PUK. Če jo želite odkleniti, vnesite kodo PUK."</string> <string name="needPuk2" msgid="4526033371987193070">"Če želite odstraniti blokiranje kartice SIM, vnesite PUK2."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"ID dohodnega klicatelja"</string> <string name="ClirMmi" msgid="7784673673446833091">"ID odhodnega klicatelja"</string> <string name="CfMmi" msgid="5123218989141573515">"Preusmerjanje klicev"</string> diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml index 66c05617687b..d03abd788dae 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Унесите PUK који се састоји од 8 цифара или више."</string> <string name="needPuk" msgid="919668385956251611">"SIM картица је закључана PUK кодом. Унесите PUK кôд да бисте је откључали."</string> <string name="needPuk2" msgid="4526033371987193070">"Унесите PUK2 да бисте деблокирали SIM картицу."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Долазни ИД позиваоца"</string> <string name="ClirMmi" msgid="7784673673446833091">"Одлазни ИД позиваоца"</string> <string name="CfMmi" msgid="5123218989141573515">"Преусмеравање позива"</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 6559fc58fad3..ddaa30ad47f5 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Ange en PUK-kod med minst 8 siffror."</string> <string name="needPuk" msgid="919668385956251611">"Ditt SIM-kort är PUK-låst. Ange PUK-koden om du vill låsa upp det."</string> <string name="needPuk2" msgid="4526033371987193070">"Ange PUK2-koden för att häva spärren av SIM-kortet."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Nummerpresentatör för inkommande samtal"</string> <string name="ClirMmi" msgid="7784673673446833091">"Nummerpresentatör för utgående samtal"</string> <string name="CfMmi" msgid="5123218989141573515">"Vidarebefordra samtal"</string> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index 39d2abad8f4a..04249645f722 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Andika PUK ambayo ina urefu wa nambari 8 au zaidi."</string> <string name="needPuk" msgid="919668385956251611">"Kadi yako ya SIM imefungwa na PUK. Anika msimbo wa PUK ili kuifungua."</string> <string name="needPuk2" msgid="4526033371987193070">"Chapisha PUK2 ili kufungua SIM kadi."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Kitambulisho cha Mpigaji wa Simu Inayoingia"</string> <string name="ClirMmi" msgid="7784673673446833091">"ID ya Mpigaji simu Inayotoka nje"</string> <string name="CfMmi" msgid="5123218989141573515">"Kusambaza simu"</string> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index 4b59923184da..7197f9055313 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"พิมพ์รหัส PUK ซึ่งต้องเป็นตัวเลขอย่างน้อย 8 หลัก"</string> <string name="needPuk" msgid="919668385956251611">"ซิมการ์ดของคุณถูกล็อกด้วย PUK พิมพ์รหัส PUK เพื่อปลดล็อก"</string> <string name="needPuk2" msgid="4526033371987193070">"พิมพ์ PUK2 เพื่อยกเลิกการปิดกั้นซิมการ์ด"</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"หมายเลขผู้โทรเข้า"</string> <string name="ClirMmi" msgid="7784673673446833091">"หมายเลขผู้โทรออก"</string> <string name="CfMmi" msgid="5123218989141573515">"การโอนสาย"</string> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index dc22a0864cec..ff2e7ea97cb9 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Mag-type ng PUK na may 8 numbero o mas mahaba."</string> <string name="needPuk" msgid="919668385956251611">"Na-PUK-lock ang iyong SIM card. I-type ang PUK code upang i-unlock ito."</string> <string name="needPuk2" msgid="4526033371987193070">"I-type ang PUK2 upang i-unblock ang SIM card."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Papasok na Caller ID"</string> <string name="ClirMmi" msgid="7784673673446833091">"Papalabas na Caller ID"</string> <string name="CfMmi" msgid="5123218989141573515">"Pagpapasa ng tawag"</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 419ae9663471..c4507612c3e5 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"8 veya daha uzun basamaklı bir PUK kodu yazın."</string> <string name="needPuk" msgid="919668385956251611">"SIM kartınızın PUK kilidi devrede. Kilidi açmak için PUK kodunu yazın."</string> <string name="needPuk2" msgid="4526033371987193070">"Engellenen SIM kartı açmak için PUK2 kodunu yazın."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Gelen Çağrı Kimliği"</string> <string name="ClirMmi" msgid="7784673673446833091">"Giden Çağrı Kimliği"</string> <string name="CfMmi" msgid="5123218989141573515">"Çağrı yönlendirme"</string> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index 13311f130073..2afb95f0536a 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Введіть PUK-код із 8 або більше цифр."</string> <string name="needPuk" msgid="919668385956251611">"SIM-карта заблок. PUK-кодом. Введіть PUK-код, щоб її розблок."</string> <string name="needPuk2" msgid="4526033371987193070">"Введ. PUK2, щоб розбл. SIM-карту."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Вхідн. ід. абонента"</string> <string name="ClirMmi" msgid="7784673673446833091">"Вихід. ід. абонента"</string> <string name="CfMmi" msgid="5123218989141573515">"Переадрес. виклику"</string> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index b94f104daaa3..329917ecd988 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Nhập PUK có từ 8 số trở lên."</string> <string name="needPuk" msgid="919668385956251611">"Thẻ SIM của bạn đã bị khóa PUK. Nhập mã PUK để mở khóa thẻ SIM đó."</string> <string name="needPuk2" msgid="4526033371987193070">"Nhập mã PUK2 để bỏ chặn thẻ SIM."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"Số gọi đến"</string> <string name="ClirMmi" msgid="7784673673446833091">"Số gọi đi"</string> <string name="CfMmi" msgid="5123218989141573515">"Chuyển tiếp cuộc gọi"</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index d7a1e43b3de6..d6ed9a950757 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"请键入至少 8 位数字的 PUK 码。"</string> <string name="needPuk" msgid="919668385956251611">"已对 SIM 卡进行 PUK 码锁定。键入 PUK 码将其解锁。"</string> <string name="needPuk2" msgid="4526033371987193070">"输入 PUK2 码以解锁 SIM 卡。"</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"来电显示"</string> <string name="ClirMmi" msgid="7784673673446833091">"本机号码"</string> <string name="CfMmi" msgid="5123218989141573515">"来电转接"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 447af8aa225a..bc9337b95f13 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"輸入 8 位數以上的 PUK。"</string> <string name="needPuk" msgid="919668385956251611">"SIM 卡的 PUK 已鎖定。請輸入 PUK 碼解除鎖定。"</string> <string name="needPuk2" msgid="4526033371987193070">"請輸入 PUK2 以解鎖 SIM 卡。"</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"來電顯示"</string> <string name="ClirMmi" msgid="7784673673446833091">"本機號碼"</string> <string name="CfMmi" msgid="5123218989141573515">"來電轉接"</string> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index f43835f2664d..6471965b129c 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -50,6 +50,10 @@ <string name="invalidPuk" msgid="8761456210898036513">"Thayipha i-PUK enezinombolo ezingu-8 noma ngaphezu."</string> <string name="needPuk" msgid="919668385956251611">"Ikhadi lakho le-SIM livalwe nge-PUK. Thayipha ikhodi ye-PUK ukulivula."</string> <string name="needPuk2" msgid="4526033371987193070">"Thayipha i-PUK2 ukuze uvule ikhadi le-SIM."</string> + <!-- no translation found for imei (2625429890869005782) --> + <skip /> + <!-- no translation found for meid (4841221237681254195) --> + <skip /> <string name="ClipMmi" msgid="6952821216480289285">"I-ID Yocingo Olungenayo"</string> <string name="ClirMmi" msgid="7784673673446833091">"I-ID Yomshayeli Ephumayo"</string> <string name="CfMmi" msgid="5123218989141573515">"Ukudlulisa ikholi"</string> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 428790f6b734..438c14104944 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -1506,6 +1506,14 @@ <enum name="KEYCODE_CALENDAR" value="208" /> <enum name="KEYCODE_MUSIC" value="209" /> <enum name="KEYCODE_CALCULATOR" value="210" /> + <enum name="KEYCODE_ZENKAKU_HANKAKU" value="211" /> + <enum name="KEYCODE_EISU" value="212" /> + <enum name="KEYCODE_MUHENKAN" value="213" /> + <enum name="KEYCODE_HENKAN" value="214" /> + <enum name="KEYCODE_KATAKANA_HIRAGANA" value="215" /> + <enum name="KEYCODE_YEN" value="216" /> + <enum name="KEYCODE_RO" value="217" /> + <enum name="KEYCODE_KANA" value="218" /> </attr> <!-- ***************************************************************** --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 9c5f4d6ad05d..b60cda7e1a72 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -34,6 +34,7 @@ <java-symbol type="id" name="account_type" /> <java-symbol type="id" name="action_bar" /> <java-symbol type="id" name="action_bar_container" /> + <java-symbol type="id" name="action_bar_overlay_layout" /> <java-symbol type="id" name="action_bar_title" /> <java-symbol type="id" name="action_bar_subtitle" /> <java-symbol type="id" name="action_context_bar" /> @@ -183,6 +184,7 @@ <java-symbol type="id" name="to_common" /> <java-symbol type="id" name="to_org" /> <java-symbol type="id" name="to_org_unit" /> + <java-symbol type="id" name="top_action_bar" /> <java-symbol type="id" name="topPanel" /> <java-symbol type="id" name="up" /> <java-symbol type="id" name="value" /> @@ -850,6 +852,8 @@ <java-symbol type="string" name="wifi_watchdog_network_disabled" /> <java-symbol type="string" name="wifi_watchdog_network_disabled_detailed" /> <java-symbol type="string" name="yesterday" /> + <java-symbol type="string" name="imei" /> + <java-symbol type="string" name="meid" /> <java-symbol type="plurals" name="abbrev_in_num_days" /> <java-symbol type="plurals" name="abbrev_in_num_hours" /> @@ -1009,7 +1013,6 @@ <java-symbol type="layout" name="select_dialog" /> <java-symbol type="layout" name="simple_dropdown_hint" /> <java-symbol type="layout" name="status_bar_latest_event_content" /> - <java-symbol type="layout" name="status_bar_latest_event_content_large_icon" /> <java-symbol type="layout" name="status_bar_latest_event_ticker" /> <java-symbol type="layout" name="status_bar_latest_event_ticker_large_icon" /> <java-symbol type="layout" name="text_edit_action_popup_text" /> @@ -1071,7 +1074,9 @@ <java-symbol type="layout" name="zoom_container" /> <java-symbol type="layout" name="zoom_controls" /> <java-symbol type="layout" name="zoom_magnify" /> + <java-symbol type="layout" name="notification_action" /> <java-symbol type="layout" name="notification_intruder_content" /> + <java-symbol type="layout" name="notification_template_base" /> <java-symbol type="layout" name="notification_template_big_picture" /> <java-symbol type="anim" name="slide_in_child_bottom" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 7799f749209c..89196642b471 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -97,6 +97,12 @@ the SIM card. --> <string name="needPuk">Your SIM card is PUK-locked. Type the PUK code to unlock it.</string> <string name="needPuk2">Type PUK2 to unblock SIM card.</string> + <!-- Title for the dialog used to display the user's IMEI number [CHAR LIMIT=10] --> + <string name="imei">IMEI</string> + + <!-- Title for the dialog used to display the user's MEID number on CDMA network + [CHAR LIMIT=10] --> + <string name="meid">MEID</string> <!-- Displayed as the title for a success/failure report enabling/disabling caller ID. --> <string name="ClipMmi">Incoming Caller ID</string> @@ -3186,42 +3192,42 @@ <string name="add_account_button_label">Add account</string> <!-- NumberPicker - accessibility support --> - <!-- Description of the button to increment the NumberPicker value. [CHAR LIMIT=NONE] --> - <string name="number_picker_increment_button">Increment</string> - <!-- Description of the button to decrement the NumberPicker value. [CHAR LIMIT=NONE] --> - <string name="number_picker_decrement_button">Decrement</string> + <!-- Description of the button to increase the NumberPicker value. [CHAR LIMIT=NONE] --> + <string name="number_picker_increment_button">Increase</string> + <!-- Description of the button to decrease the NumberPicker value. [CHAR LIMIT=NONE] --> + <string name="number_picker_decrement_button">Decrease</string> <!-- Description of the tap and hold action to get into scroll mode in NumberPicker. [CHAR LIMIT=NONE] --> <string name="number_picker_increment_scroll_mode"><xliff:g id="value" example="3">%s</xliff:g> touch and hold.</string> <!-- Description of the scrolling action in NumberPicker. [CHAR LIMIT=NONE] --> - <string name="number_picker_increment_scroll_action">Slide up to increment and down to decrement.</string> + <string name="number_picker_increment_scroll_action">Slide up to increase and down to decrease.</string> <!-- TimePicker - accessibility support --> - <!-- Description of the button to increment the TimePicker's minute value. [CHAR LIMIT=NONE] --> - <string name="time_picker_increment_minute_button">Increment minute</string> - <!-- Description of the button to decrement the TimePicker's minute value. [CHAR LIMIT=NONE] --> - <string name="time_picker_decrement_minute_button">Decrement minute</string> - <!-- Description of the button to increment the TimePicker's hour value. [CHAR LIMIT=NONE] --> - <string name="time_picker_increment_hour_button">Increment hour</string> - <!-- Description of the button to decrement the TimePicker's hour value. [CHAR LIMIT=NONE] --> - <string name="time_picker_decrement_hour_button">Decrement hour</string> - <!-- Description of the button to increment the TimePicker's set PM value. [CHAR LIMIT=NONE] --> + <!-- Description of the button to increase the TimePicker's minute value. [CHAR LIMIT=NONE] --> + <string name="time_picker_increment_minute_button">Increase minute</string> + <!-- Description of the button to decrease the TimePicker's minute value. [CHAR LIMIT=NONE] --> + <string name="time_picker_decrement_minute_button">Decrease minute</string> + <!-- Description of the button to increase the TimePicker's hour value. [CHAR LIMIT=NONE] --> + <string name="time_picker_increment_hour_button">Increase hour</string> + <!-- Description of the button to decrease the TimePicker's hour value. [CHAR LIMIT=NONE] --> + <string name="time_picker_decrement_hour_button">Decrease hour</string> + <!-- Description of the button to increase the TimePicker's set PM value. [CHAR LIMIT=NONE] --> <string name="time_picker_increment_set_pm_button">Set PM</string> - <!-- Description of the button to decrement the TimePicker's set AM value. [CHAR LIMIT=NONE] --> + <!-- Description of the button to decrease the TimePicker's set AM value. [CHAR LIMIT=NONE] --> <string name="time_picker_decrement_set_am_button">Set AM</string> <!-- DatePicker - accessibility support --> - <!-- Description of the button to increment the DatePicker's month value. [CHAR LIMIT=NONE] --> - <string name="date_picker_increment_month_button">Increment month</string> - <!-- Description of the button to decrement the DatePicker's month value. [CHAR LIMIT=NONE] --> - <string name="date_picker_decrement_month_button">Decrement month</string> - <!-- Description of the button to increment the DatePicker's day value. [CHAR LIMIT=NONE] --> - <string name="date_picker_increment_day_button">Increment day</string> - <!-- Description of the button to decrement the DatePicker's day value. [CHAR LIMIT=NONE] --> - <string name="date_picker_decrement_day_button">Decrement day</string> - <!-- Description of the button to increment the DatePicker's year value. [CHAR LIMIT=NONE] --> - <string name="date_picker_increment_year_button">Increment year</string> - <!-- Description of the button to decrement the DatePicker's year value. [CHAR LIMIT=NONE] --> - <string name="date_picker_decrement_year_button">Decrement year</string> + <!-- Description of the button to increase the DatePicker's month value. [CHAR LIMIT=NONE] --> + <string name="date_picker_increment_month_button">Increase month</string> + <!-- Description of the button to decrease the DatePicker's month value. [CHAR LIMIT=NONE] --> + <string name="date_picker_decrement_month_button">Decrease month</string> + <!-- Description of the button to increase the DatePicker's day value. [CHAR LIMIT=NONE] --> + <string name="date_picker_increment_day_button">Increase day</string> + <!-- Description of the button to decrease the DatePicker's day value. [CHAR LIMIT=NONE] --> + <string name="date_picker_decrement_day_button">Decrease day</string> + <!-- Description of the button to increase the DatePicker's year value. [CHAR LIMIT=NONE] --> + <string name="date_picker_increment_year_button">Increase year</string> + <!-- Description of the button to decrease the DatePicker's year value. [CHAR LIMIT=NONE] --> + <string name="date_picker_decrement_year_button">Decrease year</string> <!-- CheckBox - accessibility support --> <!-- Description of the checked state of a CheckBox. [CHAR LIMIT=NONE] --> @@ -3279,13 +3285,13 @@ <string name="content_description_sliding_handle">"Sliding handle. Touch & hold."</string> <!-- Description of the up direction in which one can to slide the handle in the Slide unlock screen. [CHAR LIMIT=NONE] --> - <string name="description_direction_up">Up for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string> + <string name="description_direction_up">Slide up for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string> <!-- Description of the down direction in which one can to slide the handle in the Slide unlock screen. [CHAR LIMIT=NONE] --> - <string name="description_direction_down">Down for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string> + <string name="description_direction_down">Slide down for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string> <!-- Description of the left direction in which one can to slide the handle in the Slide unlock screen. [CHAR LIMIT=NONE] --> - <string name="description_direction_left">"Left for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string> + <string name="description_direction_left">"Slide left for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string> <!-- Description of the right direction in which one can to slide the handle in the Slide unlock screen. [CHAR LIMIT=NONE] --> - <string name="description_direction_right">Right for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string> + <string name="description_direction_right">Slide right for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string> <!-- Description of the unlock target in the Slide unlock screen. [CHAR LIMIT=NONE] --> <string name="description_target_unlock">Unlock</string> diff --git a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java index 6a471add46d9..b2075aeec54d 100644 --- a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java +++ b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java @@ -16,12 +16,8 @@ package android.app; -import coretestutils.http.MockResponse; -import coretestutils.http.MockWebServer; - import android.app.DownloadManager.Query; import android.app.DownloadManager.Request; -import android.app.DownloadManagerBaseTest.DataType; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -39,10 +35,14 @@ import android.provider.Settings; import android.test.InstrumentationTestCase; import android.util.Log; +import com.google.mockwebserver.MockResponse; +import com.google.mockwebserver.MockWebServer; + import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; @@ -53,13 +53,15 @@ import java.util.Random; import java.util.Set; import java.util.concurrent.TimeoutException; +import libcore.io.Streams; + /** * Base class for Instrumented tests for the Download Manager. */ public class DownloadManagerBaseTest extends InstrumentationTestCase { private static final String TAG = "DownloadManagerBaseTest"; protected DownloadManager mDownloadManager = null; - protected MockWebServer mServer = null; + private MockWebServer mServer = null; protected String mFileType = "text/plain"; protected Context mContext = null; protected MultipleDownloadsCompletedReceiver mReceiver = null; @@ -237,63 +239,57 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase { mContext = getInstrumentation().getContext(); mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE); mServer = new MockWebServer(); + mServer.play(); mReceiver = registerNewMultipleDownloadsReceiver(); // Note: callers overriding this should call mServer.play() with the desired port # } /** - * Helper to enqueue a response from the MockWebServer with no body. + * Helper to build a response from the MockWebServer with no body. * * @param status The HTTP status code to return for this response * @return Returns the mock web server response that was queued (which can be modified) */ - protected MockResponse enqueueResponse(int status) { - return doEnqueueResponse(status); - + protected MockResponse buildResponse(int status) { + MockResponse response = new MockResponse().setResponseCode(status); + response.setHeader("Content-type", mFileType); + return response; } /** - * Helper to enqueue a response from the MockWebServer. + * Helper to build a response from the MockWebServer. * * @param status The HTTP status code to return for this response * @param body The body to return in this response * @return Returns the mock web server response that was queued (which can be modified) */ - protected MockResponse enqueueResponse(int status, byte[] body) { - return doEnqueueResponse(status).setBody(body); - + protected MockResponse buildResponse(int status, byte[] body) { + return buildResponse(status).setBody(body); } /** - * Helper to enqueue a response from the MockWebServer. + * Helper to build a response from the MockWebServer. * * @param status The HTTP status code to return for this response * @param bodyFile The body to return in this response * @return Returns the mock web server response that was queued (which can be modified) */ - protected MockResponse enqueueResponse(int status, File bodyFile) { - return doEnqueueResponse(status).setBody(bodyFile); + protected MockResponse buildResponse(int status, File bodyFile) + throws FileNotFoundException, IOException { + final byte[] body = Streams.readFully(new FileInputStream(bodyFile)); + return buildResponse(status).setBody(body); } - /** - * Helper for enqueue'ing a response from the MockWebServer. - * - * @param status The HTTP status code to return for this response - * @return Returns the mock web server response that was queued (which can be modified) - */ - protected MockResponse doEnqueueResponse(int status) { - MockResponse response = new MockResponse().setResponseCode(status); - response.addHeader("Content-type", mFileType); - mServer.enqueue(response); - return response; + protected void enqueueResponse(MockResponse resp) { + mServer.enqueue(resp); } /** * Helper to generate a random blob of bytes. * * @param size The size of the data to generate - * @param type The type of data to generate: currently, one of {@link DataType.TEXT} or - * {@link DataType.BINARY}. + * @param type The type of data to generate: currently, one of {@link DataType#TEXT} or + * {@link DataType#BINARY}. * @return The random data that is generated. */ protected byte[] generateData(int size, DataType type) { @@ -304,8 +300,8 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase { * Helper to generate a random blob of bytes using a given RNG. * * @param size The size of the data to generate - * @param type The type of data to generate: currently, one of {@link DataType.TEXT} or - * {@link DataType.BINARY}. + * @param type The type of data to generate: currently, one of {@link DataType#TEXT} or + * {@link DataType#BINARY}. * @param rng (optional) The RNG to use; pass null to use * @return The random data that is generated. */ @@ -492,8 +488,6 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase { assertEquals(1, cursor.getCount()); assertTrue(cursor.moveToFirst()); - mServer.checkForExceptions(); - verifyFileSize(pfd, fileSize); verifyFileContents(pfd, fileData); } finally { @@ -928,7 +922,7 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase { protected long enqueueDownloadRequest(byte[] body, int location) throws Exception { // Prepare the mock server with a standard response - enqueueResponse(HTTP_OK, body); + mServer.enqueue(buildResponse(HTTP_OK, body)); return doEnqueue(location); } @@ -943,7 +937,7 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase { protected long enqueueDownloadRequest(File body, int location) throws Exception { // Prepare the mock server with a standard response - enqueueResponse(HTTP_OK, body); + mServer.enqueue(buildResponse(HTTP_OK, body)); return doEnqueue(location); } @@ -1035,4 +1029,4 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase { assertEquals(1, mReceiver.numDownloadsCompleted()); return dlRequest; } -}
\ No newline at end of file +} diff --git a/core/tests/coretests/src/android/app/DownloadManagerFunctionalTest.java b/core/tests/coretests/src/android/app/DownloadManagerFunctionalTest.java index afe7f5566a44..aa9f69dc84b1 100644 --- a/core/tests/coretests/src/android/app/DownloadManagerFunctionalTest.java +++ b/core/tests/coretests/src/android/app/DownloadManagerFunctionalTest.java @@ -16,8 +16,6 @@ package android.app; -import coretestutils.http.MockResponse; - import android.app.DownloadManager.Query; import android.app.DownloadManager.Request; import android.database.Cursor; @@ -26,6 +24,8 @@ import android.os.Environment; import android.os.ParcelFileDescriptor; import android.test.suitebuilder.annotation.LargeTest; +import com.google.mockwebserver.MockResponse; + import java.io.File; import java.util.Iterator; import java.util.Set; @@ -47,7 +47,6 @@ public class DownloadManagerFunctionalTest extends DownloadManagerBaseTest { public void setUp() throws Exception { super.setUp(); setWiFiStateOn(true); - mServer.play(); removeAllCurrentDownloads(); } @@ -132,8 +131,6 @@ public class DownloadManagerFunctionalTest extends DownloadManagerBaseTest { assertEquals(1, cursor.getCount()); assertTrue(cursor.moveToFirst()); - mServer.checkForExceptions(); - verifyFileSize(pfd, fileSize); verifyFileContents(pfd, fileData); int colIndex = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME); @@ -154,7 +151,7 @@ public class DownloadManagerFunctionalTest extends DownloadManagerBaseTest { byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT); // Prepare the mock server with a standard response - enqueueResponse(HTTP_OK, blobData); + enqueueResponse(buildResponse(HTTP_OK, blobData)); try { Uri uri = getServerUri(DEFAULT_FILENAME); @@ -193,7 +190,7 @@ public class DownloadManagerFunctionalTest extends DownloadManagerBaseTest { byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT); // Prepare the mock server with a standard response - enqueueResponse(HTTP_OK, blobData); + enqueueResponse(buildResponse(HTTP_OK, blobData)); Uri uri = getServerUri(DEFAULT_FILENAME); Request request = new Request(uri); @@ -224,7 +221,7 @@ public class DownloadManagerFunctionalTest extends DownloadManagerBaseTest { byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT); // Prepare the mock server with a standard response - enqueueResponse(HTTP_OK, blobData); + enqueueResponse(buildResponse(HTTP_OK, blobData)); Uri uri = getServerUri(DEFAULT_FILENAME); Request request = new Request(uri); @@ -251,7 +248,7 @@ public class DownloadManagerFunctionalTest extends DownloadManagerBaseTest { public void testGetDownloadIdOnNotification() throws Exception { byte[] blobData = generateData(3000, DataType.TEXT); // file size = 3000 bytes - MockResponse response = enqueueResponse(HTTP_OK, blobData); + enqueueResponse(buildResponse(HTTP_OK, blobData)); long dlRequest = doCommonStandardEnqueue(); waitForDownloadOrTimeout(dlRequest); @@ -271,8 +268,9 @@ public class DownloadManagerFunctionalTest extends DownloadManagerBaseTest { // force 6 redirects for (int i = 0; i < 6; ++i) { - MockResponse response = enqueueResponse(HTTP_REDIRECT); - response.addHeader("Location", uri.toString()); + final MockResponse resp = buildResponse(HTTP_REDIRECT); + resp.setHeader("Location", uri.toString()); + enqueueResponse(resp); } doErrorTest(uri, DownloadManager.ERROR_TOO_MANY_REDIRECTS); } @@ -283,7 +281,7 @@ public class DownloadManagerFunctionalTest extends DownloadManagerBaseTest { @LargeTest public void testErrorUnhandledHttpCode() throws Exception { Uri uri = getServerUri(DEFAULT_FILENAME); - MockResponse response = enqueueResponse(HTTP_PARTIAL_CONTENT); + enqueueResponse(buildResponse(HTTP_PARTIAL_CONTENT)); doErrorTest(uri, DownloadManager.ERROR_UNHANDLED_HTTP_CODE); } @@ -294,8 +292,9 @@ public class DownloadManagerFunctionalTest extends DownloadManagerBaseTest { @LargeTest public void testErrorHttpDataError_invalidRedirect() throws Exception { Uri uri = getServerUri(DEFAULT_FILENAME); - MockResponse response = enqueueResponse(HTTP_REDIRECT); - response.addHeader("Location", "://blah.blah.blah.com"); + final MockResponse resp = buildResponse(HTTP_REDIRECT); + resp.setHeader("Location", "://blah.blah.blah.com"); + enqueueResponse(resp); doErrorTest(uri, DownloadManager.ERROR_HTTP_DATA_ERROR); } @@ -327,7 +326,7 @@ public class DownloadManagerFunctionalTest extends DownloadManagerBaseTest { public void testSetTitle() throws Exception { int fileSize = 1024; byte[] blobData = generateData(fileSize, DataType.BINARY); - MockResponse response = enqueueResponse(HTTP_OK, blobData); + enqueueResponse(buildResponse(HTTP_OK, blobData)); // An arbitrary unicode string title final String title = "\u00a5123;\"\u0152\u017d \u054b \u0a07 \ucce0 \u6820\u03a8\u5c34" + @@ -359,7 +358,7 @@ public class DownloadManagerFunctionalTest extends DownloadManagerBaseTest { byte[] blobData = generateData(fileSize, DataType.TEXT); setWiFiStateOn(false); - enqueueResponse(HTTP_OK, blobData); + enqueueResponse(buildResponse(HTTP_OK, blobData)); try { Uri uri = getServerUri(DEFAULT_FILENAME); @@ -383,32 +382,16 @@ public class DownloadManagerFunctionalTest extends DownloadManagerBaseTest { } /** - * Tests when the server drops the connection after all headers (but before any data send). - */ - @LargeTest - public void testDropConnection_headers() throws Exception { - byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT); - - MockResponse response = enqueueResponse(HTTP_OK, blobData); - response.setCloseConnectionAfterHeader("content-length"); - long dlRequest = doCommonStandardEnqueue(); - - // Download will never complete when header is dropped - boolean success = waitForDownloadOrTimeoutNoThrow(dlRequest, DEFAULT_WAIT_POLL_TIME, - DEFAULT_MAX_WAIT_TIME); - - assertFalse(success); - } - - /** * Tests that we get an error code when the server drops the connection during a download. */ @LargeTest public void testServerDropConnection_body() throws Exception { byte[] blobData = generateData(25000, DataType.TEXT); // file size = 25000 bytes - MockResponse response = enqueueResponse(HTTP_OK, blobData); - response.setCloseConnectionAfterXBytes(15382); + final MockResponse resp = buildResponse(HTTP_OK, blobData); + resp.setHeader("Content-Length", "50000"); + enqueueResponse(resp); + long dlRequest = doCommonStandardEnqueue(); waitForDownloadOrTimeout(dlRequest); diff --git a/core/tests/coretests/src/android/app/DownloadManagerStressTest.java b/core/tests/coretests/src/android/app/DownloadManagerStressTest.java index bdeb5544fad9..864b2d6ebce1 100644 --- a/core/tests/coretests/src/android/app/DownloadManagerStressTest.java +++ b/core/tests/coretests/src/android/app/DownloadManagerStressTest.java @@ -46,7 +46,6 @@ public class DownloadManagerStressTest extends DownloadManagerBaseTest { public void setUp() throws Exception { super.setUp(); setWiFiStateOn(true); - mServer.play(); removeAllCurrentDownloads(); } @@ -85,7 +84,7 @@ public class DownloadManagerStressTest extends DownloadManagerBaseTest { request.setTitle(String.format("%s--%d", DEFAULT_FILENAME + i, i)); // Prepare the mock server with a standard response - enqueueResponse(HTTP_OK, blobData); + enqueueResponse(buildResponse(HTTP_OK, blobData)); long requestID = mDownloadManager.enqueue(request); } @@ -127,7 +126,7 @@ public class DownloadManagerStressTest extends DownloadManagerBaseTest { try { long dlRequest = doStandardEnqueue(largeFile); - // wait for the download to complete + // wait for the download to complete waitForDownloadOrTimeout(dlRequest); ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest); diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk b/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk index a419068c633c..09dcac5f1547 100644 --- a/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk +++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk @@ -20,7 +20,7 @@ LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under, src) -LOCAL_STATIC_JAVA_LIBRARIES := android-common frameworks-core-util-lib +LOCAL_STATIC_JAVA_LIBRARIES := android-common mockwebserver LOCAL_SDK_VERSION := current LOCAL_PACKAGE_NAME := DownloadManagerTestApp diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerBaseTest.java b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/downloadmanagertests/DownloadManagerBaseTest.java index 334661d9c697..8e935f83fae9 100644 --- a/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerBaseTest.java +++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/downloadmanagertests/DownloadManagerBaseTest.java @@ -18,7 +18,6 @@ package com.android.frameworks.downloadmanagertests; import android.app.DownloadManager; import android.app.DownloadManager.Query; -import android.app.DownloadManager.Request; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -26,37 +25,19 @@ import android.content.IntentFilter; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.NetworkInfo; -import android.net.Uri; import android.net.wifi.WifiManager; -import android.os.Bundle; import android.os.Environment; import android.os.ParcelFileDescriptor; import android.os.SystemClock; -import android.os.ParcelFileDescriptor.AutoCloseInputStream; import android.provider.Settings; import android.test.InstrumentationTestCase; import android.util.Log; -import java.io.DataInputStream; -import java.io.DataOutputStream; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.URL; -import java.util.concurrent.TimeoutException; import java.util.Collections; import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Random; import java.util.Set; -import java.util.Vector; - -import junit.framework.AssertionFailedError; - -import coretestutils.http.MockResponse; -import coretestutils.http.MockWebServer; +import java.util.concurrent.TimeoutException; /** * Base class for Instrumented tests for the Download Manager. @@ -64,7 +45,6 @@ import coretestutils.http.MockWebServer; public class DownloadManagerBaseTest extends InstrumentationTestCase { protected DownloadManager mDownloadManager = null; - protected MockWebServer mServer = null; protected String mFileType = "text/plain"; protected Context mContext = null; protected MultipleDownloadsCompletedReceiver mReceiver = null; @@ -77,7 +57,6 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase { protected static final int HTTP_PARTIAL_CONTENT = 206; protected static final int HTTP_NOT_FOUND = 404; protected static final int HTTP_SERVICE_UNAVAILABLE = 503; - protected String DEFAULT_FILENAME = "somefile.txt"; protected static final int DEFAULT_MAX_WAIT_TIME = 2 * 60 * 1000; // 2 minutes protected static final int DEFAULT_WAIT_POLL_TIME = 5 * 1000; // 5 seconds @@ -86,48 +65,6 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase { protected static final int MAX_WAIT_FOR_DOWNLOAD_TIME = 5 * 60 * 1000; // 5 minutes protected static final int MAX_WAIT_FOR_LARGE_DOWNLOAD_TIME = 15 * 60 * 1000; // 15 minutes - protected static final int DOWNLOAD_TO_SYSTEM_CACHE = 1; - protected static final int DOWNLOAD_TO_DOWNLOAD_CACHE_DIR = 2; - - // Just a few popular file types used to return from a download - protected enum DownloadFileType { - PLAINTEXT, - APK, - GIF, - GARBAGE, - UNRECOGNIZED, - ZIP - } - - protected enum DataType { - TEXT, - BINARY - } - - public static class LoggingRng extends Random { - - /** - * Constructor - * - * Creates RNG with self-generated seed value. - */ - public LoggingRng() { - this(SystemClock.uptimeMillis()); - } - - /** - * Constructor - * - * Creats RNG with given initial seed value - - * @param seed The initial seed value - */ - public LoggingRng(long seed) { - super(seed); - Log.i(LOG_TAG, "Seeding RNG with value: " + seed); - } - } - public static class MultipleDownloadsCompletedReceiver extends BroadcastReceiver { private volatile int mNumDownloadsCompleted = 0; private Set<Long> downloadIds = Collections.synchronizedSet(new HashSet<Long>()); @@ -171,7 +108,7 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase { /** * Gets the number of times the {@link #onReceive} callback has been called for the - * {@link DownloadManager.ACTION_DOWNLOAD_COMPLETED} action, indicating the number of + * {@link DownloadManager#ACTION_DOWNLOAD_COMPLETE} action, indicating the number of * downloads completed thus far. * * @return the number of downloads completed so far. @@ -241,76 +178,7 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase { public void setUp() throws Exception { mContext = getInstrumentation().getContext(); mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE); - mServer = new MockWebServer(); mReceiver = registerNewMultipleDownloadsReceiver(); - // Note: callers overriding this should call mServer.play() with the desired port # - } - - /** - * Helper to enqueue a response from the MockWebServer. - * - * @param status The HTTP status code to return for this response - * @param body The body to return in this response - * @return Returns the mock web server response that was queued (which can be modified) - */ - private MockResponse enqueueResponse(int status, byte[] body) { - return doEnqueueResponse(status).setBody(body); - - } - - /** - * Helper to enqueue a response from the MockWebServer. - * - * @param status The HTTP status code to return for this response - * @param bodyFile The body to return in this response - * @return Returns the mock web server response that was queued (which can be modified) - */ - private MockResponse enqueueResponse(int status, File bodyFile) { - return doEnqueueResponse(status).setBody(bodyFile); - } - - /** - * Helper for enqueue'ing a response from the MockWebServer. - * - * @param status The HTTP status code to return for this response - * @return Returns the mock web server response that was queued (which can be modified) - */ - private MockResponse doEnqueueResponse(int status) { - MockResponse response = new MockResponse().setResponseCode(status); - response.addHeader("Content-type", mFileType); - mServer.enqueue(response); - return response; - } - - /** - * Helper to generate a random blob of bytes using a given RNG. - * - * @param size The size of the data to generate - * @param type The type of data to generate: currently, one of {@link DataType.TEXT} or - * {@link DataType.BINARY}. - * @param rng (optional) The RNG to use; pass null to use - * @return The random data that is generated. - */ - private byte[] generateData(int size, DataType type, Random rng) { - int min = Byte.MIN_VALUE; - int max = Byte.MAX_VALUE; - - // Only use chars in the HTTP ASCII printable character range for Text - if (type == DataType.TEXT) { - min = 32; - max = 126; - } - byte[] result = new byte[size]; - Log.i(LOG_TAG, "Generating data of size: " + size); - - if (rng == null) { - rng = new LoggingRng(); - } - - for (int i = 0; i < size; ++i) { - result[i] = (byte) (min + rng.nextInt(max - min + 1)); - } - return result; } /** @@ -324,76 +192,6 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase { } /** - * Helper to verify the contents of a downloaded file versus a byte[]. - * - * @param actual The file of whose contents to verify - * @param expected The data we expect to find in the aforementioned file - * @throws IOException if there was a problem reading from the file - */ - private void verifyFileContents(ParcelFileDescriptor actual, byte[] expected) - throws IOException { - AutoCloseInputStream input = new ParcelFileDescriptor.AutoCloseInputStream(actual); - long fileSize = actual.getStatSize(); - - assertTrue(fileSize <= Integer.MAX_VALUE); - assertEquals(expected.length, fileSize); - - byte[] actualData = new byte[expected.length]; - assertEquals(input.read(actualData), fileSize); - compareByteArrays(actualData, expected); - } - - /** - * Helper to compare 2 byte arrays. - * - * @param actual The array whose data we want to verify - * @param expected The array of data we expect to see - */ - private void compareByteArrays(byte[] actual, byte[] expected) { - assertEquals(actual.length, expected.length); - int length = actual.length; - for (int i = 0; i < length; ++i) { - // assert has a bit of overhead, so only do the assert when the values are not the same - if (actual[i] != expected[i]) { - fail("Byte arrays are not equal."); - } - } - } - - /** - * Gets the MIME content string for a given type - * - * @param type The MIME type to return - * @return the String representation of that MIME content type - */ - protected String getMimeMapping(DownloadFileType type) { - switch (type) { - case APK: - return "application/vnd.android.package-archive"; - case GIF: - return "image/gif"; - case ZIP: - return "application/x-zip-compressed"; - case GARBAGE: - return "zip\\pidy/doo/da"; - case UNRECOGNIZED: - return "application/new.undefined.type.of.app"; - } - return "text/plain"; - } - - /** - * Gets the Uri that should be used to access the mock server - * - * @param filename The name of the file to try to retrieve from the mock server - * @return the Uri to use for access the file on the mock server - */ - private Uri getServerUri(String filename) throws Exception { - URL url = mServer.getUrl("/" + filename); - return Uri.parse(url.toString()); - } - - /** * Helper to create and register a new MultipleDownloadCompletedReciever * * This is used to track many simultaneous downloads by keeping count of all the downloads @@ -738,39 +536,6 @@ public class DownloadManagerBaseTest extends InstrumentationTestCase { } /** - * Helper to perform a standard enqueue of data to the mock server. - * download is performed to the downloads cache dir (NOT systemcache dir) - * - * @param body The body to return in the response from the server - */ - private long doStandardEnqueue(byte[] body) throws Exception { - // Prepare the mock server with a standard response - enqueueResponse(HTTP_OK, body); - return doCommonStandardEnqueue(); - } - - /** - * Helper to perform a standard enqueue of data to the mock server. - * - * @param body The body to return in the response from the server, contained in the file - */ - private long doStandardEnqueue(File body) throws Exception { - // Prepare the mock server with a standard response - enqueueResponse(HTTP_OK, body); - return doCommonStandardEnqueue(); - } - - /** - * Helper to do the additional steps (setting title and Uri of default filename) when - * doing a standard enqueue request to the server. - */ - private long doCommonStandardEnqueue() throws Exception { - Uri uri = getServerUri(DEFAULT_FILENAME); - Request request = new Request(uri).setTitle(DEFAULT_FILENAME); - return mDownloadManager.enqueue(request); - } - - /** * Helper to verify an int value in a Cursor * * @param cursor The cursor containing the query results diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/downloadmanagertests/DownloadManagerTestApp.java index 654f74794ac3..9c44d6117dfe 100644 --- a/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java +++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/downloadmanagertests/DownloadManagerTestApp.java @@ -16,16 +16,11 @@ package com.android.frameworks.downloadmanagertests; import android.app.DownloadManager; -import android.app.DownloadManager.Query; import android.app.DownloadManager.Request; -import android.content.Context; -import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Environment; import android.os.ParcelFileDescriptor; -import android.provider.Settings; -import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; import java.io.DataInputStream; @@ -33,13 +28,8 @@ import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.FileWriter; import java.util.HashSet; -import coretestutils.http.MockResponse; -import coretestutils.http.MockWebServer; -import coretestutils.http.RecordedRequest; - /** * Class to test downloading files from a real (not mock) external server. */ @@ -243,7 +233,7 @@ public class DownloadManagerTestApp extends DownloadManagerBaseTest { Uri remoteUri = getExternalFileUri(filename); Request request = new Request(remoteUri); - request.setMimeType(getMimeMapping(DownloadFileType.APK)); + request.setMimeType("application/vnd.android.package-archive"); dlRequest = mDownloadManager.enqueue(request); diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestRunner.java b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/downloadmanagertests/DownloadManagerTestRunner.java index 27bf7e1e2497..27bf7e1e2497 100644 --- a/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestRunner.java +++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/downloadmanagertests/DownloadManagerTestRunner.java diff --git a/core/tests/utillib/src/coretestutils/http/MockResponse.java b/core/tests/utillib/src/coretestutils/http/MockResponse.java deleted file mode 100644 index 5b03e5fe9019..000000000000 --- a/core/tests/utillib/src/coretestutils/http/MockResponse.java +++ /dev/null @@ -1,239 +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 coretestutils.http; - -import static coretestutils.http.MockWebServer.ASCII; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import android.util.Log; - -/** - * A scripted response to be replayed by the mock web server. - */ -public class MockResponse { - private static final byte[] EMPTY_BODY = new byte[0]; - static final String LOG_TAG = "coretestutils.http.MockResponse"; - - private String status = "HTTP/1.1 200 OK"; - private Map<String, String> headers = new HashMap<String, String>(); - private byte[] body = EMPTY_BODY; - private boolean closeConnectionAfter = false; - private String closeConnectionAfterHeader = null; - private int closeConnectionAfterXBytes = -1; - private int pauseConnectionAfterXBytes = -1; - private File bodyExternalFile = null; - - public MockResponse() { - addHeader("Content-Length", 0); - } - - /** - * Returns the HTTP response line, such as "HTTP/1.1 200 OK". - */ - public String getStatus() { - return status; - } - - public MockResponse setResponseCode(int code) { - this.status = "HTTP/1.1 " + code + " OK"; - return this; - } - - /** - * Returns the HTTP headers, such as "Content-Length: 0". - */ - public List<String> getHeaders() { - List<String> headerStrings = new ArrayList<String>(); - for (String header : headers.keySet()) { - headerStrings.add(header + ": " + headers.get(header)); - } - return headerStrings; - } - - public MockResponse addHeader(String header, String value) { - headers.put(header.toLowerCase(), value); - return this; - } - - public MockResponse addHeader(String header, long value) { - return addHeader(header, Long.toString(value)); - } - - public MockResponse removeHeader(String header) { - headers.remove(header.toLowerCase()); - return this; - } - - /** - * Returns true if the body should come from an external file, false otherwise. - */ - private boolean bodyIsExternal() { - return bodyExternalFile != null; - } - - /** - * Returns an input stream containing the raw HTTP payload. - */ - public InputStream getBody() { - if (bodyIsExternal()) { - try { - return new FileInputStream(bodyExternalFile); - } catch (FileNotFoundException e) { - Log.e(LOG_TAG, "File not found: " + bodyExternalFile.getAbsolutePath()); - } - } - return new ByteArrayInputStream(this.body); - } - - public MockResponse setBody(File body) { - addHeader("Content-Length", body.length()); - this.bodyExternalFile = body; - return this; - } - - public MockResponse setBody(byte[] body) { - addHeader("Content-Length", body.length); - this.body = body; - return this; - } - - public MockResponse setBody(String body) { - try { - return setBody(body.getBytes(ASCII)); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(); - } - } - - /** - * Sets the body as chunked. - * - * Currently chunked body is not supported for external files as bodies. - */ - public MockResponse setChunkedBody(byte[] body, int maxChunkSize) throws IOException { - addHeader("Transfer-encoding", "chunked"); - - ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); - int pos = 0; - while (pos < body.length) { - int chunkSize = Math.min(body.length - pos, maxChunkSize); - bytesOut.write(Integer.toHexString(chunkSize).getBytes(ASCII)); - bytesOut.write("\r\n".getBytes(ASCII)); - bytesOut.write(body, pos, chunkSize); - bytesOut.write("\r\n".getBytes(ASCII)); - pos += chunkSize; - } - bytesOut.write("0\r\n".getBytes(ASCII)); - this.body = bytesOut.toByteArray(); - return this; - } - - public MockResponse setChunkedBody(String body, int maxChunkSize) throws IOException { - return setChunkedBody(body.getBytes(ASCII), maxChunkSize); - } - - @Override public String toString() { - return status; - } - - public boolean shouldCloseConnectionAfter() { - return closeConnectionAfter; - } - - public MockResponse setCloseConnectionAfter(boolean closeConnectionAfter) { - this.closeConnectionAfter = closeConnectionAfter; - return this; - } - - /** - * Sets the header after which sending the server should close the connection. - */ - public MockResponse setCloseConnectionAfterHeader(String header) { - closeConnectionAfterHeader = header; - setCloseConnectionAfter(true); - return this; - } - - /** - * Returns the header after which sending the server should close the connection. - */ - public String getCloseConnectionAfterHeader() { - return closeConnectionAfterHeader; - } - - /** - * Sets the number of bytes in the body to send before which the server should close the - * connection. Set to -1 to unset and send the entire body (default). - */ - public MockResponse setCloseConnectionAfterXBytes(int position) { - closeConnectionAfterXBytes = position; - setCloseConnectionAfter(true); - return this; - } - - /** - * Returns the number of bytes in the body to send before which the server should close the - * connection. Returns -1 if the entire body should be sent (default). - */ - public int getCloseConnectionAfterXBytes() { - return closeConnectionAfterXBytes; - } - - /** - * Sets the number of bytes in the body to send before which the server should pause the - * connection (stalls in sending data). Only one pause per response is supported. - * Set to -1 to unset pausing (default). - */ - public MockResponse setPauseConnectionAfterXBytes(int position) { - pauseConnectionAfterXBytes = position; - return this; - } - - /** - * Returns the number of bytes in the body to send before which the server should pause the - * connection (stalls in sending data). (Returns -1 if it should not pause). - */ - public int getPauseConnectionAfterXBytes() { - return pauseConnectionAfterXBytes; - } - - /** - * Returns true if this response is flagged to pause the connection mid-stream, false otherwise - */ - public boolean getShouldPause() { - return (pauseConnectionAfterXBytes != -1); - } - - /** - * Returns true if this response is flagged to close the connection mid-stream, false otherwise - */ - public boolean getShouldClose() { - return (closeConnectionAfterXBytes != -1); - } -} diff --git a/core/tests/utillib/src/coretestutils/http/MockWebServer.java b/core/tests/utillib/src/coretestutils/http/MockWebServer.java deleted file mode 100644 index c329ffa518c1..000000000000 --- a/core/tests/utillib/src/coretestutils/http/MockWebServer.java +++ /dev/null @@ -1,426 +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 coretestutils.http; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.MalformedURLException; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.URL; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import android.util.Log; - -/** - * A scriptable web server. Callers supply canned responses and the server - * replays them upon request in sequence. - * - * TODO: merge with the version from libcore/support/src/tests/java once it's in. - */ -public final class MockWebServer { - static final String ASCII = "US-ASCII"; - static final String LOG_TAG = "coretestutils.http.MockWebServer"; - - private final BlockingQueue<RecordedRequest> requestQueue - = new LinkedBlockingQueue<RecordedRequest>(); - private final BlockingQueue<MockResponse> responseQueue - = new LinkedBlockingQueue<MockResponse>(); - private int bodyLimit = Integer.MAX_VALUE; - private final ExecutorService executor = Executors.newCachedThreadPool(); - // keep Futures around so we can rethrow any exceptions thrown by Callables - private final Queue<Future<?>> futures = new LinkedList<Future<?>>(); - private final Object downloadPauseLock = new Object(); - // global flag to signal when downloads should resume on the server - private volatile boolean downloadResume = false; - - private int port = -1; - - public int getPort() { - if (port == -1) { - throw new IllegalStateException("Cannot retrieve port before calling play()"); - } - return port; - } - - /** - * Returns a URL for connecting to this server. - * - * @param path the request path, such as "/". - */ - public URL getUrl(String path) throws MalformedURLException { - return new URL("http://localhost:" + getPort() + path); - } - - /** - * Sets the number of bytes of the POST body to keep in memory to the given - * limit. - */ - public void setBodyLimit(int maxBodyLength) { - this.bodyLimit = maxBodyLength; - } - - public void enqueue(MockResponse response) { - responseQueue.add(response); - } - - /** - * Awaits the next HTTP request, removes it, and returns it. Callers should - * use this to verify the request sent was as intended. - */ - public RecordedRequest takeRequest() throws InterruptedException { - return requestQueue.take(); - } - - public RecordedRequest takeRequestWithTimeout(long timeoutMillis) throws InterruptedException { - return requestQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS); - } - - public List<RecordedRequest> drainRequests() { - List<RecordedRequest> requests = new ArrayList<RecordedRequest>(); - requestQueue.drainTo(requests); - return requests; - } - - /** - * Starts the server, serves all enqueued requests, and shuts the server - * down using the default (server-assigned) port. - */ - public void play() throws IOException { - play(0); - } - - /** - * Starts the server, serves all enqueued requests, and shuts the server - * down. - * - * @param port The port number to use to listen to connections on; pass in 0 to have the - * server automatically assign a free port - */ - public void play(int portNumber) throws IOException { - final ServerSocket ss = new ServerSocket(portNumber); - ss.setReuseAddress(true); - port = ss.getLocalPort(); - submitCallable(new Callable<Void>() { - public Void call() throws Exception { - int count = 0; - while (true) { - if (count > 0 && responseQueue.isEmpty()) { - ss.close(); - executor.shutdown(); - return null; - } - - serveConnection(ss.accept()); - count++; - } - } - }); - } - - private void serveConnection(final Socket s) { - submitCallable(new Callable<Void>() { - public Void call() throws Exception { - InputStream in = new BufferedInputStream(s.getInputStream()); - OutputStream out = new BufferedOutputStream(s.getOutputStream()); - - int sequenceNumber = 0; - while (true) { - RecordedRequest request = readRequest(in, sequenceNumber); - if (request == null) { - if (sequenceNumber == 0) { - throw new IllegalStateException("Connection without any request!"); - } else { - break; - } - } - requestQueue.add(request); - MockResponse response = computeResponse(request); - writeResponse(out, response); - if (response.shouldCloseConnectionAfter()) { - break; - } - sequenceNumber++; - } - - in.close(); - out.close(); - return null; - } - }); - } - - private void submitCallable(Callable<?> callable) { - Future<?> future = executor.submit(callable); - futures.add(future); - } - - /** - * Check for and raise any exceptions that have been thrown by child threads. Will not block on - * children still running. - * @throws ExecutionException for the first child thread that threw an exception - */ - public void checkForExceptions() throws ExecutionException, InterruptedException { - final int originalSize = futures.size(); - for (int i = 0; i < originalSize; i++) { - Future<?> future = futures.remove(); - try { - future.get(0, TimeUnit.SECONDS); - } catch (TimeoutException e) { - futures.add(future); // still running - } - } - } - - /** - * @param sequenceNumber the index of this request on this connection. - */ - private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException { - String request = readAsciiUntilCrlf(in); - if (request.equals("")) { - return null; // end of data; no more requests - } - - List<String> headers = new ArrayList<String>(); - int contentLength = -1; - boolean chunked = false; - String header; - while (!(header = readAsciiUntilCrlf(in)).equals("")) { - headers.add(header); - String lowercaseHeader = header.toLowerCase(); - if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) { - contentLength = Integer.parseInt(header.substring(15).trim()); - } - if (lowercaseHeader.startsWith("transfer-encoding:") && - lowercaseHeader.substring(18).trim().equals("chunked")) { - chunked = true; - } - } - - boolean hasBody = false; - TruncatingOutputStream requestBody = new TruncatingOutputStream(); - List<Integer> chunkSizes = new ArrayList<Integer>(); - if (contentLength != -1) { - hasBody = true; - transfer(contentLength, in, requestBody); - } else if (chunked) { - hasBody = true; - while (true) { - int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16); - if (chunkSize == 0) { - readEmptyLine(in); - break; - } - chunkSizes.add(chunkSize); - transfer(chunkSize, in, requestBody); - readEmptyLine(in); - } - } - - if (request.startsWith("GET ")) { - if (hasBody) { - throw new IllegalArgumentException("GET requests should not have a body!"); - } - } else if (request.startsWith("POST ")) { - if (!hasBody) { - throw new IllegalArgumentException("POST requests must have a body!"); - } - } else { - throw new UnsupportedOperationException("Unexpected method: " + request); - } - - return new RecordedRequest(request, headers, chunkSizes, - requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber); - } - - /** - * Returns a response to satisfy {@code request}. - */ - private MockResponse computeResponse(RecordedRequest request) throws InterruptedException { - if (responseQueue.isEmpty()) { - throw new IllegalStateException("Unexpected request: " + request); - } - return responseQueue.take(); - } - - private void writeResponse(OutputStream out, MockResponse response) throws IOException { - out.write((response.getStatus() + "\r\n").getBytes(ASCII)); - boolean doCloseConnectionAfterHeader = (response.getCloseConnectionAfterHeader() != null); - - // Send headers - String closeConnectionAfterHeader = response.getCloseConnectionAfterHeader(); - for (String header : response.getHeaders()) { - out.write((header + "\r\n").getBytes(ASCII)); - - if (doCloseConnectionAfterHeader && header.startsWith(closeConnectionAfterHeader)) { - Log.i(LOG_TAG, "Closing connection after header" + header); - break; - } - } - - // Send actual body data - if (!doCloseConnectionAfterHeader) { - out.write(("\r\n").getBytes(ASCII)); - - InputStream body = response.getBody(); - final int READ_BLOCK_SIZE = 10000; // process blocks this size - byte[] currentBlock = new byte[READ_BLOCK_SIZE]; - int currentBlockSize = 0; - int writtenSoFar = 0; - - boolean shouldPause = response.getShouldPause(); - boolean shouldClose = response.getShouldClose(); - int pause = response.getPauseConnectionAfterXBytes(); - int close = response.getCloseConnectionAfterXBytes(); - - // Don't bother pausing if it's set to pause -after- the connection should be dropped - if (shouldPause && shouldClose && (pause > close)) { - shouldPause = false; - } - - // Process each block we read in... - while ((currentBlockSize = body.read(currentBlock)) != -1) { - int startIndex = 0; - int writeLength = currentBlockSize; - - // handle the case of pausing - if (shouldPause && (writtenSoFar + currentBlockSize >= pause)) { - writeLength = pause - writtenSoFar; - out.write(currentBlock, 0, writeLength); - out.flush(); - writtenSoFar += writeLength; - - // now pause... - try { - Log.i(LOG_TAG, "Pausing connection after " + pause + " bytes"); - // Wait until someone tells us to resume sending... - synchronized(downloadPauseLock) { - while (!downloadResume) { - downloadPauseLock.wait(); - } - // reset resume back to false - downloadResume = false; - } - } catch (InterruptedException e) { - Log.e(LOG_TAG, "Server was interrupted during pause in download."); - } - - startIndex = writeLength; - writeLength = currentBlockSize - writeLength; - } - - // handle the case of closing the connection - if (shouldClose && (writtenSoFar + writeLength > close)) { - writeLength = close - writtenSoFar; - out.write(currentBlock, startIndex, writeLength); - writtenSoFar += writeLength; - Log.i(LOG_TAG, "Closing connection after " + close + " bytes"); - break; - } - out.write(currentBlock, startIndex, writeLength); - writtenSoFar += writeLength; - } - } - out.flush(); - } - - /** - * Transfer bytes from {@code in} to {@code out} until either {@code length} - * bytes have been transferred or {@code in} is exhausted. - */ - private void transfer(int length, InputStream in, OutputStream out) throws IOException { - byte[] buffer = new byte[1024]; - while (length > 0) { - int count = in.read(buffer, 0, Math.min(buffer.length, length)); - if (count == -1) { - return; - } - out.write(buffer, 0, count); - length -= count; - } - } - - /** - * Returns the text from {@code in} until the next "\r\n", or null if - * {@code in} is exhausted. - */ - private String readAsciiUntilCrlf(InputStream in) throws IOException { - StringBuilder builder = new StringBuilder(); - while (true) { - int c = in.read(); - if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') { - builder.deleteCharAt(builder.length() - 1); - return builder.toString(); - } else if (c == -1) { - return builder.toString(); - } else { - builder.append((char) c); - } - } - } - - private void readEmptyLine(InputStream in) throws IOException { - String line = readAsciiUntilCrlf(in); - if (!line.equals("")) { - throw new IllegalStateException("Expected empty but was: " + line); - } - } - - /** - * An output stream that drops data after bodyLimit bytes. - */ - private class TruncatingOutputStream extends ByteArrayOutputStream { - private int numBytesReceived = 0; - @Override public void write(byte[] buffer, int offset, int len) { - numBytesReceived += len; - super.write(buffer, offset, Math.min(len, bodyLimit - count)); - } - @Override public void write(int oneByte) { - numBytesReceived++; - if (count < bodyLimit) { - super.write(oneByte); - } - } - } - - /** - * Trigger the server to resume sending the download - */ - public void doResumeDownload() { - synchronized (downloadPauseLock) { - downloadResume = true; - downloadPauseLock.notifyAll(); - } - } -} diff --git a/core/tests/utillib/src/coretestutils/http/RecordedRequest.java b/core/tests/utillib/src/coretestutils/http/RecordedRequest.java deleted file mode 100644 index 293ff80dc6e9..000000000000 --- a/core/tests/utillib/src/coretestutils/http/RecordedRequest.java +++ /dev/null @@ -1,93 +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 coretestutils.http; - -import java.util.List; - -/** - * An HTTP request that came into the mock web server. - */ -public final class RecordedRequest { - private final String requestLine; - private final List<String> headers; - private final List<Integer> chunkSizes; - private final int bodySize; - private final byte[] body; - private final int sequenceNumber; - - RecordedRequest(String requestLine, List<String> headers, List<Integer> chunkSizes, - int bodySize, byte[] body, int sequenceNumber) { - this.requestLine = requestLine; - this.headers = headers; - this.chunkSizes = chunkSizes; - this.bodySize = bodySize; - this.body = body; - this.sequenceNumber = sequenceNumber; - } - - public String getRequestLine() { - return requestLine; - } - - public List<String> getHeaders() { - return headers; - } - - /** - * Returns the sizes of the chunks of this request's body, or an empty list - * if the request's body was empty or unchunked. - */ - public List<Integer> getChunkSizes() { - return chunkSizes; - } - - /** - * Returns the total size of the body of this POST request (before - * truncation). - */ - public int getBodySize() { - return bodySize; - } - - /** - * Returns the body of this POST request. This may be truncated. - */ - public byte[] getBody() { - return body; - } - - /** - * Returns the index of this request on its HTTP connection. Since a single - * HTTP connection may serve multiple requests, each request is assigned its - * own sequence number. - */ - public int getSequenceNumber() { - return sequenceNumber; - } - - @Override public String toString() { - return requestLine; - } - - public String getMethod() { - return getRequestLine().split(" ")[0]; - } - - public String getPath() { - return getRequestLine().split(" ")[1]; - } -} diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl index fdd9040d7348..768ee5e99ff6 100644 --- a/data/keyboards/Generic.kl +++ b/data/keyboards/Generic.kl @@ -104,16 +104,16 @@ key 81 NUMPAD_3 key 82 NUMPAD_0 key 83 NUMPAD_DOT # key 84 (undefined) -# key 85 "KEY_ZENKAKUHANKAKU" +key 85 ZENKAKU_HANKAKU key 86 BACKSLASH key 87 F11 key 88 F12 -# key 89 "KEY_RO" +key 89 RO # key 90 "KEY_KATAKANA" # key 91 "KEY_HIRAGANA" -# key 92 "KEY_HENKAN" -# key 93 "KEY_KATAKANAHIRAGANA" -# key 94 "KEY_MUHENKAN" +key 92 HENKAN +key 93 KATAKANA_HIRAGANA +key 94 MUHENKAN key 95 NUMPAD_COMMA key 96 NUMPAD_ENTER key 97 CTRL_RIGHT @@ -141,9 +141,9 @@ key 117 NUMPAD_EQUALS key 119 BREAK # key 120 (undefined) key 121 NUMPAD_COMMA -# key 122 "KEY_HANGEUL" -# key 123 "KEY_HANJA" -# key 124 "KEY_YEN" +key 122 KANA +key 123 EISU +key 124 YEN key 125 META_LEFT key 126 META_RIGHT key 127 MENU WAKE_DROPPED diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs index 9d5064e4f863..92bc83ec8617 100644 --- a/docs/html/guide/guide_toc.cs +++ b/docs/html/guide/guide_toc.cs @@ -259,13 +259,13 @@ <li class="toggle-list"> <div><a href="<?cs var:toroot ?>guide/topics/graphics/index.html"> <span class="en">Graphics</span> - </a> <span class="new-child">new!</span></div> + </a></div> <ul> <li><a href="<?cs var:toroot ?>guide/topics/graphics/2d-graphics.html"> <span class="en">Canvas and Drawables</span></a></li> <li><a href="<?cs var:toroot ?>guide/topics/graphics/hardware-accel.html"> <span class="en">Hardware Acceleration</span></a> - <span class="new">new!</span></li> + </li> <li><a href="<?cs var:toroot ?>guide/topics/graphics/opengl.html"> <span class="en">OpenGL</span> </a></li> @@ -287,7 +287,6 @@ <li class="toggle-list"> <div><a href="<?cs var:toroot ?>guide/topics/renderscript/index.html"> <span class="en">Renderscript</span></a> - <span class="new">updated</span> </div> <ul> <li><a href="<?cs var:toroot ?>guide/topics/renderscript/graphics.html"> @@ -318,7 +317,6 @@ </li> <li><a href="<?cs var:toroot ?>guide/topics/media/camera.html"> <span class="en">Camera</span></a> - <span class="new">new!</span> </li> <li><a href="<?cs var:toroot ?>guide/topics/media/audio-capture.html"> <span class="en">Audio Capture</span></a> @@ -332,7 +330,7 @@ <li class="toggle-list"> <div><a href="<?cs var:toroot ?>guide/topics/sensors/index.html"> <span class="en">Sensors</span> - </a> <span class="new">new!</span></div> + </a></div> <ul> <li><a href="<?cs var:toroot ?>guide/topics/sensors/sensors_overview.html"> <span class="en">Sensors Overview</span> @@ -373,7 +371,7 @@ </li> <li class="toggle-list"> <div><a href="<?cs var:toroot?>guide/topics/nfc/index.html"> - <span class="en">Near Field Communication</span></a> <span class="new">updated</span> + <span class="en">Near Field Communication</span></a> </div> <ul> <li><a href="<?cs var:toroot ?>guide/topics/nfc/nfc.html">NFC Basics</a></li> @@ -381,7 +379,7 @@ </ul> </li> <li><a href="<?cs var:toroot?>guide/topics/wireless/wifip2p.html"> - <span class="en">Wi-Fi Direct</span></a> <span class="new">new!</span> + <span class="en">Wi-Fi Direct</span></a> </li> <li class="toggle-list"> <div><a href="<?cs var:toroot?>guide/topics/usb/index.html"> @@ -788,7 +786,7 @@ applications</span> </li> <li><a href="<?cs var:toroot ?>guide/practices/tablets-and-handsets.html"> <span class="en">Supporting Tablets and Handsets</span> - </a> <span class="new">new!</span></li> + </a></li> <li class="toggle-list"> <div><a href="<?cs var:toroot ?>guide/practices/ui_guidelines/index.html"> <span class="en">UI Guidelines</span> diff --git a/docs/html/guide/publishing/app-signing.jd b/docs/html/guide/publishing/app-signing.jd index e86ec301c25c..5bd9be559497 100644 --- a/docs/html/guide/publishing/app-signing.jd +++ b/docs/html/guide/publishing/app-signing.jd @@ -66,7 +66,7 @@ on an emulator or a device if it is not signed.</li> application's signer certificate expires after the application is installed, the application will continue to function normally.</li> <li>You can use standard tools — Keytool and Jarsigner — to generate keys and -sign your application .apk files.</li> +sign your application {@code .apk} files.</li> <li>After you sign your application for release, we recommend that you use the <code>zipalign</code> tool to optimize the final APK package.</li> </ul> @@ -186,9 +186,9 @@ to the Keytool in the JDK.</p> <p>The Android build tools provide a debug signing mode that makes it easier for you to develop and debug your application, while still meeting the Android system -requirement for signing your .apk. +requirement for signing your APK. When using debug mode to build your app, the SDK tools invoke Keytool to automatically create -a debug keystore and key. This debug key is then used to automatically sign the .apk, so +a debug keystore and key. This debug key is then used to automatically sign the APK, so you do not need to sign the package with your own key.</p> <p>The SDK tools create the debug keystore/key with predetermined names/passwords:</p> @@ -215,19 +215,19 @@ to the public when signed with the debug certificate.</p> <p>If you are developing in Eclipse/ADT (and have set up Keytool and Jarsigner as described above in <a href="#setup">Basic Setup for Signing</a>), signing in debug mode is enabled by default. When you run or debug your -application, ADT signs the .apk with the debug certificate, runs {@code zipalign} on the -package, then installs it on +application, ADT signs the {@code .apk} file with the debug certificate, runs {@code zipalign} on +the package, then installs it on the selected emulator or connected device. No specific action on your part is needed, provided ADT has access to Keytool.</p> <h3>Ant Users</h3> -<p>If you are using Ant to build your .apk files, debug signing mode +<p>If you are using Ant to build your {@code .apk} file, debug signing mode is enabled by using the <code>debug</code> option with the <code>ant</code> command (assuming that you are using a <code>build.xml</code> file generated by the <code>android</code> tool). When you run <code>ant debug</code> to -compile your app, the build script generates a keystore/key and signs the .apk for you. -The script then also aligns the .apk with the <code>zipalign</code> tool. +compile your app, the build script generates a keystore/key and signs the APK for you. +The script then also aligns the APK with the <code>zipalign</code> tool. No other action on your part is needed. Read <a href="{@docRoot}guide/developing/building/building-cmdline.html#DebugMode">Building and Running Apps on the Command Line</a> for more information.</p> @@ -383,8 +383,8 @@ will use later, to refer to this keystore when signing your application. </p> <p>For more information about Keytool, see the documentation at <a -href="http://java.sun.com/j2se/1.5.0/docs/tooldocs/#security"> -http://java.sun.com/j2se/1.5.0/docs/tooldocs/#security</a></p> +href="http://docs.oracle.com/javase/6/docs/technotes/tools/windows/keytool.html"> +http://docs.oracle.com/javase/6/docs/technotes/tools/windows/keytool.html</a></p> @@ -399,11 +399,11 @@ You can not release your application unsigned, or signed with the debug key.</p> <h4>With Eclipse</h4> -<p>To export an <em>unsigned</em> .apk from Eclipse, right-click the project in the Package +<p>To export an <em>unsigned</em> APK from Eclipse, right-click the project in the Package Explorer and select <strong>Android Tools</strong> > <strong>Export Unsigned Application -Package</strong>. Then specify the file location for the unsigned .apk. -(Alternatively, open your <code>AndroidManifest.xml</code> file in Eclipse, open -the <em>Overview</em> tab, and click <strong>Export an unsigned .apk</strong>.)</p> +Package</strong>. Then specify the file location for the unsigned APK. +(Alternatively, open your <code>AndroidManifest.xml</code> file in Eclipse, select +the <strong>Manifest</strong> tab, and click <strong>Export an unsigned APK</strong>.)</p> <p>Note that you can combine the compiling and signing steps with the Export Wizard. See <a href="#ExportWizard">Compiling and signing with Eclipse ADT</a>.</p> @@ -414,11 +414,11 @@ the <em>Overview</em> tab, and click <strong>Export an unsigned .apk</strong>.)< with the <code>ant</code> command. For example, if you are running Ant from the directory containing your {@code build.xml} file, the command would look like this:</p> -<pre>ant release</pre> +<pre>$ ant release</pre> -<p>By default, the build script compiles the application .apk without signing it. The output file +<p>By default, the build script compiles the application APK without signing it. The output file in your project {@code bin/} will be <code><em><your_project_name></em>-unsigned.apk</code>. -Because the application .apk is still unsigned, you must manually sign it with your private +Because the application APK is still unsigned, you must manually sign it with your private key and then align it using {@code zipalign}.</p> <p>However, the Ant build script can also perform the signing @@ -443,8 +443,8 @@ machine, as described in <a href="#setup">Basic Setup</a>. Also, make sure that the keystore containing your private key is available.</p> <p>To sign your application, you run Jarsigner, referencing both the -application's .apk and the keystore containing the private key with which to -sign the .apk. The table below shows the options you could use. </p> +application's APK and the keystore containing the private key with which to +sign the APK. The table below shows the options you could use. </p> <table> <tr> @@ -459,6 +459,14 @@ the keystore containing your private key.</td> <td><code>-verbose</code></td><td>Enable verbose output.</td> </tr> <tr> +<td><code>-sigalg</code></td><td>The name of the signature algorithim to use in signing the APK. +Use the value {@code MD5withRSA}.</td> +</tr> +<tr> +<td><code>-digestalg</code></td><td>The message digest algorithim to use in processing the entries +of an APK. Use the value {@code SHA1}.</td> +</tr> +<tr> <td><code>-storepass <password></code></td><td><p>The password for the keystore. </p><p>As a security precaution, do not include this option in your command line unless you are working at a secure computer. @@ -478,19 +486,23 @@ way, your password is not stored in your shell history.</p></td> <code>my_application.apk</code>, using the example keystore created above. </p> -<pre>$ jarsigner -verbose -keystore my-release-key.keystore +<pre>$ jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore my-release-key.keystore my_application.apk alias_name</pre> <p>Running the example command above, Jarsigner prompts you to provide -passwords for the keystore and key. It then modifies the .apk -in-place, meaning the .apk is now signed. Note that you can sign an -.apk multiple times with different keys.</p> +passwords for the keystore and key. It then modifies the APK +in-place, meaning the APK is now signed. Note that you can sign an +APK multiple times with different keys.</p> + +<p class="caution"><strong>Caution:</strong> As of JDK 7, the default signing algorithim has +changed, requiring you to specify the signature and digest algorithims ({@code -sigalg} and {@code +-digestalg}) when you sign an APK.</p> -<p>To verify that your .apk is signed, you can use a command like this:</p> +<p>To verify that your APK is signed, you can use a command like this:</p> <pre>$ jarsigner -verify my_signed.apk</pre> -<p>If the .apk is signed properly, Jarsigner prints "jar verified". +<p>If the APK is signed properly, Jarsigner prints "jar verified". If you want more details, you can try one of these commands:</p> <pre>$ jarsigner -verify -verbose my_application.apk</pre> @@ -502,19 +514,19 @@ If you want more details, you can try one of these commands:</p> <p>The command above, with the <code>-certs</code> option added, will show you the "CN=" line that describes who created the key.</p> -<p class="note"><strong>Note:</strong> If you see "CN=Android Debug", this means the .apk was +<p class="note"><strong>Note:</strong> If you see "CN=Android Debug", this means the APK was signed with the debug key generated by the Android SDK. If you intend to release your application, you must sign it with your private key instead of the debug key.</p> <p>For more information about Jarsigner, see the documentation at -<a href="http://java.sun.com/j2se/1.5.0/docs/tooldocs/#security"> -http://java.sun.com/j2se/1.5.0/docs/tooldocs/#security</a></p> +<a href="http://docs.oracle.com/javase/6/docs/technotes/tools/windows/jarsigner.html"> +http://docs.oracle.com/javase/6/docs/technotes/tools/windows/jarsigner.html</a></p> <h3 id="align">4. Align the final APK package</h3> -<p>Once you have signed the .apk with your private key, run <code>zipalign</code> on the file. +<p>Once you have signed the APK with your private key, run <code>zipalign</code> on the file. This tool ensures that all uncompressed data starts with a particular byte alignment, relative to the start of the file. Ensuring alignment at 4-byte boundaries provides a performance optimization when installed on a device. When aligned, the Android @@ -524,16 +536,16 @@ of the data from the package. The benefit is a reduction in the amount of RAM consumed by the running application.</p> <p>The <code>zipalign</code> tool is provided with the Android SDK, inside the -<code>tools/</code> directory. To align your signed .apk, execute:</p> +<code>tools/</code> directory. To align your signed APK, execute:</p> -<pre>zipalign -v 4 <em>your_project_name</em>-unaligned.apk <em>your_project_name</em>.apk</pre> +<pre>$ zipalign -v 4 <em>your_project_name</em>-unaligned.apk <em>your_project_name</em>.apk</pre> <p>The {@code -v} flag turns on verbose output (optional). {@code 4} is the byte-alignment (don't use anything other than 4). The first file argument is -your signed .apk (the input) and the second file is the destination .apk file (the output). -If you're overriding an existing .apk, add the {@code -f} flag.</p> +your signed {@code .apk} file (the input) and the second file is the destination {@code .apk} file +(the output). If you're overriding an existing APK, add the {@code -f} flag.</p> -<p class="caution"><strong>Caution:</strong> Your input .apk must be signed with your +<p class="caution"><strong>Caution:</strong> Your input APK must be signed with your private key <strong>before</strong> you optimize the package with {@code zipalign}. If you sign it after using {@code zipalign}, it will undo the alignment.</p> @@ -544,7 +556,7 @@ If you sign it after using {@code zipalign}, it will undo the alignment.</p> <h3 id="ExportWizard">Compile and sign with Eclipse ADT</h3> <p>If you are using Eclipse with the ADT plugin, you can use the Export Wizard to -export a <em>signed</em> .apk (and even create a new keystore, +export a <em>signed</em> APK (and even create a new keystore, if necessary). The Export Wizard performs all the interaction with the Keytool and Jarsigner for you, which allows you to sign the package using a GUI instead of performing the manual procedures to compile, sign, @@ -554,7 +566,7 @@ Because the Export Wizard uses both Keytool and Jarsigner, you should ensure that they are accessible on your computer, as described above in the <a href="#setup">Basic Setup for Signing</a>.</p> -<p>To create a signed and aligned .apk in Eclipse:</p> +<p>To create a signed and aligned APK in Eclipse:</p> <ol> <li>Select the project in the Package @@ -563,7 +575,7 @@ Explorer and select <strong>File > Export</strong>.</li> and click <strong>Next</strong>. <p>The Export Android Application wizard now starts, which will guide you through the process of signing your application, - including steps for selecting the private key with which to sign the .apk + including steps for selecting the private key with which to sign the APK (or creating a new keystore and private key).</p> <li>Complete the Export Wizard and your application will be compiled, signed, aligned, and ready for distribution.</li> diff --git a/docs/html/resources/articles/images/spellcheck_client_flow.png b/docs/html/resources/articles/images/spellcheck_client_flow.png Binary files differnew file mode 100644 index 000000000000..4e097aa13533 --- /dev/null +++ b/docs/html/resources/articles/images/spellcheck_client_flow.png diff --git a/docs/html/resources/articles/images/spellcheck_lifecycle.png b/docs/html/resources/articles/images/spellcheck_lifecycle.png Binary files differnew file mode 100644 index 000000000000..0b1082439f72 --- /dev/null +++ b/docs/html/resources/articles/images/spellcheck_lifecycle.png diff --git a/docs/html/resources/articles/images/textview_spellcheck_screenshot_1.png b/docs/html/resources/articles/images/textview_spellcheck_screenshot_1.png Binary files differnew file mode 100644 index 000000000000..deb47c4fdf37 --- /dev/null +++ b/docs/html/resources/articles/images/textview_spellcheck_screenshot_1.png diff --git a/docs/html/resources/articles/images/textview_spellcheck_screenshot_2.png b/docs/html/resources/articles/images/textview_spellcheck_screenshot_2.png Binary files differnew file mode 100644 index 000000000000..e3af4c5e9f82 --- /dev/null +++ b/docs/html/resources/articles/images/textview_spellcheck_screenshot_2.png diff --git a/docs/html/resources/articles/index.jd b/docs/html/resources/articles/index.jd index 220a4ed2cf0a..2947e4a2649a 100644 --- a/docs/html/resources/articles/index.jd +++ b/docs/html/resources/articles/index.jd @@ -47,7 +47,16 @@ parent.link=../browser.html?tag=article <dt><a href="{@docRoot}resources/articles/glsurfaceview.html">Introducing GLSurfaceView</a></dt> <dd>This article provides an overview of GLSurfaceView, a class that makes it easy to implement 2D or 3D OpenGL rendering inside of an Android application.</dd> </dl> - +<dl> + <dt> + <a href="{@docRoot}resources/articles/spell-checker-framework.jd"> + Using the Spell Checker Framework</a> + </dt> + <dd> + This article describes how to use the Spell Checker Framework to check spelling in + various ways in your application. + </dd> +</dl> <dl> <dt><a href="{@docRoot}resources/articles/layout-tricks-reuse.html">Layout Tricks: Creating Reusable UI Components</a></dt> <dd>Learn how to combine multiple standard UI widgets into a single high-level component, which can be reused throughout your application.</dd> @@ -149,7 +158,7 @@ parent.link=../browser.html?tag=article </dl> <dl> - <dt><a href="{@docRoot}resources/articles/window-bg-speed.html">Window Backgrounds & UI Speed</a></dt> + <dt><a href="{@docRoot}resources/articles/window-bg-speed.html">Window Backgrounds & UI Speed</a></dt> <dd>Some Android applications need to squeeze every bit of performance out of the UI toolkit and there are many ways to do so. In this article, you will discover how to speed up the drawing and the perceived startup time of your activities. Both of these techniques rely on a single feature, the window's background drawable.</dd> </dl> diff --git a/docs/html/resources/articles/spell-checker-framework.jd b/docs/html/resources/articles/spell-checker-framework.jd new file mode 100644 index 000000000000..8d57b4eb09cd --- /dev/null +++ b/docs/html/resources/articles/spell-checker-framework.jd @@ -0,0 +1,236 @@ +page.title=Using the Spell Checker Framework +parent.title=Articles +parent.link=../browser.html?tag=article +@jd:body +<div id="qv-wrapper"> +<div id="qv"> +<h2>In This Document</h2> +<ol> + <li> + <a href="#SpellCheckLifeCycle">Spell Check Lifecycle</a> + </li> + <li> + <a href="#SpellCheckImplementation">Implementing a Spell Checker Service</a> + </li> + <li> + <a href="#SpellCheckClient">Implementing a Spell Checker Client</a> + </li> +</ol> + <h2>See also</h2> + <ol> + <li> + <a href="{@docRoot}resources/samples/SpellChecker/SampleSpellCheckerService/index.html"> + Spell Checker Service</a> sample app + </li> + <li> + <a href="{@docRoot}resources/samples/SpellChecker/HelloSpellChecker/index.html"> + Spell Checker Client</a> sample app + </li> + </ol> +</div> +</div> + +<p> + The Android platform offers a spell checker framework that lets you implement + and access spell checking in your application. The framework is one of the + Text Service APIs offered by the Android platform. +</p> +<p> + To use the framework in your app, you create a special type of Android service that + generates a spell checker <strong>session</strong> object. Based on text you provide, + the session object returns spelling suggestions generated by the spell checker. +</p> +<h2 id="SpellCheckLifeCycle">Spell Checker Lifecycle</h2> +<p> + The following diagram shows the lifecycle of the spell checker service: +</p> +<img src="{@docRoot}resources/articles/images/spellcheck_lifecycle.png" alt="" height="596" + id="figure1" /> +<p class="img-caption"> + <strong>Figure 1.</strong> The spell checker service lifecycle. +</p> +<p> + To initiate spell checking, your app starts its implementation of the spell checker + service. Clients in your app, such as activities or individual UI elements, request a + spell checker session from the service, then use the session to get suggestions for text. + As a client terminates its operation, it closes its spell checker session. If necessary, your + app can shut down the spell checker service at any time. +</p> +<h2 id="SpellCheckImplementation">Implementing a Spell Checker Service</h2> +<p> + To use the spell checker framework in your app, add a spell checker service component including + the session object definition. You can also add to your app an optional activity that + controls settings. You must also add an XML metadata file that describes + the spell checker service, and add the appropriate elements to your manifest file. +</p> +<h3 id="SpellCheckCode">Spell checker classes</h3> +<p> + Define the service and session object with the following classes: +</p> +<dl> + <dt> + A subclass of {@link android.service.textservice.SpellCheckerService} + </dt> + <dd> + The {@link android.service.textservice.SpellCheckerService} implements both the + {@link android.app.Service} class and the spell checker framework interface. Within your + subclass, you must implement the following method: + <dl> + <dt>{@link android.service.textservice.SpellCheckerService#createSession()}</dt> + <dd> + A factory method that returns a + {@link android.service.textservice.SpellCheckerService.Session} object to a + client that wants to do spell checking. + </dd> + </dl> + <p> + See the + <a href="{@docRoot}resources/samples/SpellChecker/SampleSpellCheckerService/index.html"> + Spell Checker Service</a> sample app to learn more about implementing this class. + </p> + </dd> + <dt> + An implementation of {@link android.service.textservice.SpellCheckerService.Session} + </dt> + <dd> + An object that the spell checker service provides to clients, to let them pass text to + the spell checker and receive suggestions. Within this class, you must implement the + following methods: + <dl> + <dt> + {@link android.service.textservice.SpellCheckerService.Session#onCreate()} + </dt> + <dd> + Called by the system in response to + {@link android.service.textservice.SpellCheckerService#createSession()}. In this + method, you can initialize the + {@link android.service.textservice.SpellCheckerService.Session} object based on + the current locale and so forth. + </dd> + <dt> + {@link android.service.textservice.SpellCheckerService.Session#onGetSuggestions(TextInfo, int) + onGetSuggestions()} + </dt> + <dd> + Does the actual spell checking. This method returns an object containing + suggestions for the text passed to it. + </dd> + </dl> + <p> + Optionally, you can implement + {@link android.service.textservice.SpellCheckerService.Session#onCancel()}, which + handles requests to cancel spell checking, or +{@link android.service.textservice.SpellCheckerService.Session#onGetSuggestionsMultiple(TextInfo[], int, boolean) +onGetSuggestionsMultiple()}, which handles batches of suggestion requests, or both. + </p> + <p> + See the + <a href="{@docRoot}resources/samples/SpellChecker/HelloSpellChecker/index.html"> + Spell Checker Client</a> sample app to learn more about implementing this class. + </p> + </dd> +</dl> +<p class="note"> + <strong>Note:</strong> You must implement all aspects of spell checking as asynchronous and + thread-safe. A spell checker may be called simultaneously by different threads running on + different cores. The {@link android.service.textservice.SpellCheckerService} and + {@link android.service.textservice.SpellCheckerService.Session} take care of this + automatically. +</p> +<h3 id="SpellCheckXML">Spell checker manifest and metadata</h3> +<p> + In addition to code, you need to provide the appropriate manifest file and a metadata file for + the spell checker. +</p> +<p> + The manifest file defines the application, the service, and the activity for controlling + settings, as shown in the following snippet: +</p> +<pre> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.android.samplespellcheckerservice" > + <application + android:label="@string/app_name" > + <service + android:label="@string/app_name" + android:name=".SampleSpellCheckerService" + android:permission="android.permission.BIND_TEXT_SERVICE" > + <intent-filter > + <action android:name="android.service.textservice.SpellCheckerService" /> + </intent-filter> + + <meta-data + android:name="android.view.textservice.scs" + android:resource="@xml/spellchecker" /> + </service> + + <activity + android:label="@string/sample_settings" + android:name="SpellCheckerSettingsActivity" > + <intent-filter > + <action android:name="android.intent.action.MAIN" /> + </intent-filter> + </activity> + </application> +</manifest> +</pre> +<p> + Notice that components that want to use the service must request the permission + {@link android.Manifest.permission#BIND_TEXT_SERVICE} to ensure that only the system binds to + the service. The service's definition also specifies the <code>spellchecker.xml</code> metadata + file, which is described in the next section. +</p> +<p> + The metadata file <code>spellchecker.xml</code> contains the following XML: +</p> +<pre> +<spell-checker xmlns:android="http://schemas.android.com/apk/res/android" + android:label="@string/spellchecker_name" + android:settingsActivity="com.example.SpellCheckerSettingsActivity"> + <subtype + android:label="@string/subtype_generic" + android:subtypeLocale="en” + /> + <subtype + android:label="@string/subtype_generic" + android:subtypeLocale="fr” + /> +</spell-checker> +</pre> +<p> + The metadata specifies the activity that the spell checker uses for controlling settings. It + also defines subtypes for the spell checker; in this case, the subtypes define locales that + the spell checker can handle. +</p> + + +<!-- Accessing the Spell Checker Service from a Client --> +<h2 id="SpellCheckClient">Accessing the Spell Checker Service from a Client</h2> +<p> + Applications that use {@link android.widget.TextView} views automatically benefit from spell + checking, because {@link android.widget.TextView} automatically uses a spell checker. The + following screenshots show this: +</p> +<img src="{@docRoot}resources/articles/images/textview_spellcheck_screenshot_1.png" alt="" + height="45" id="figure2a" /> +<br> +<img src="{@docRoot}resources/articles/images/textview_spellcheck_screenshot_2.png" alt="" + height="121" id="figure2b" /> +<p class="img-caption"> + <strong>Figure 2.</strong> Spell checking in TextView. +</p> +<p> + However, you may want to interact directly with a spell checker service in other cases as well. + The following diagram shows the flow of control for interacting with a spell checker service: +</p> +<img src="{@docRoot}resources/articles/images/spellcheck_client_flow.png" alt="" + height="394" id="figure3" /> +<p class="img-caption"> + <strong>Figure 3.</strong> Interacting with a spell checker service. +</p> +<p> + The <a href="{@docRoot}resources/samples/SpellChecker/HelloSpellChecker/index.html"> + Spell Checker Client</a> sample app shows how to interact with a spell checker service. The + LatinIME input method editor in the Android Open Source Project also contains an example of + spell checking. +</p>
\ No newline at end of file diff --git a/docs/html/resources/dashboard/opengl.jd b/docs/html/resources/dashboard/opengl.jd index b1c32343a8d9..4c55522b743d 100644 --- a/docs/html/resources/dashboard/opengl.jd +++ b/docs/html/resources/dashboard/opengl.jd @@ -57,7 +57,7 @@ ending on the data collection date noted below.</p> <div class="dashboard-panel"> <img alt="" width="400" height="250" -src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=GL%201.1%20only|GL%202.0%20%26%201.1&chd=t%3A10.8,89.2" /> +src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=GL%201.1%20only|GL%202.0%20%26%201.1&chd=t%3A11.9,88.1" /> <table> <tr> @@ -66,14 +66,14 @@ src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl= </tr> <tr> <td>1.1 only</th> -<td>10.8%</td> +<td>11.9%</td> </tr> <tr> <td>2.0 & 1.1</th> -<td>89.2%</td> +<td>88.1%</td> </tr> </table> -<p><em>Data collected during a 7-day period ending on March 4, 2012</em></p> +<p><em>Data collected during a 7-day period ending on April 2, 2012</em></p> </div> diff --git a/docs/html/resources/dashboard/platform-versions.jd b/docs/html/resources/dashboard/platform-versions.jd index 65a55751a9fc..2cbbe99dce63 100644 --- a/docs/html/resources/dashboard/platform-versions.jd +++ b/docs/html/resources/dashboard/platform-versions.jd @@ -52,7 +52,7 @@ Google Play within a 14-day period ending on the data collection date noted belo <div class="dashboard-panel"> <img alt="" height="250" width="470" -src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:0.4,0.8,6.6,25.2,0.5,61.4,0.1,1.1,2.1,0.4,1.2&chl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3|Android%203.0|Android%203.1|Android%203.2|Android%204.0|Android%204.0.3&chco=c4df9b,6fad0c" /> +src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:0.3,0.7,6.0,23.1,0.5,63.2,0.1,1.0,2.2,0.5,2.4&chl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3|Android%203.0|Android%203.1|Android%203.2|Android%204.0|Android%204.0.3&chco=c4df9b,6fad0c" /> <table> <tr> @@ -61,25 +61,25 @@ src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:0.4,0.8,6.6,25. <th>API Level</th> <th>Distribution</th> </tr> -<tr><td><a href="{@docRoot}sdk/android-1.5.html">Android 1.5</a></td><td>Cupcake</td> <td>3</td><td>0.4%</td></tr> -<tr><td><a href="{@docRoot}sdk/android-1.6.html">Android 1.6</a></td><td>Donut</td> <td>4</td><td>0.8%</td></tr> -<tr><td><a href="{@docRoot}sdk/android-2.1.html">Android 2.1</a></td><td>Eclair</td> <td>7</td><td>6.6%</td></tr> -<tr><td><a href="{@docRoot}sdk/android-2.2.html">Android 2.2</a></td><td>Froyo</td> <td>8</td><td>25.3%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-1.5.html">Android 1.5</a></td><td>Cupcake</td> <td>3</td><td>0.3%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-1.6.html">Android 1.6</a></td><td>Donut</td> <td>4</td><td>0.7%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-2.1.html">Android 2.1</a></td><td>Eclair</td> <td>7</td><td>6.0%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-2.2.html">Android 2.2</a></td><td>Froyo</td> <td>8</td><td>23.1%</td></tr> <tr><td><a href="{@docRoot}sdk/android-2.3.html">Android 2.3 -<br/> Android 2.3.2</a></td><td rowspan="2">Gingerbread</td> <td>9</td><td>0.5%</td></tr> <tr><td><a href="{@docRoot}sdk/android-2.3.3.html">Android 2.3.3 -<br/> - Android 2.3.7</a></td><!-- Gingerbread --> <td>10</td><td>61.5%</td></tr> + Android 2.3.7</a></td><!-- Gingerbread --> <td>10</td><td>63.2%</td></tr> <tr><td><a href="{@docRoot}sdk/android-3.0.html">Android 3.0</a></td> <td rowspan="3">Honeycomb</td> <td>11</td><td>0.1%</td></tr> -<tr><td><a href="{@docRoot}sdk/android-3.1.html">Android 3.1</a></td><!-- Honeycomb --><td>12</td><td>1.1%</td></tr> -<tr><td><a href="{@docRoot}sdk/android-3.2.html">Android 3.2</a></td><!-- Honeycomb --><td>13</td><td>2.1%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-3.1.html">Android 3.1</a></td><!-- Honeycomb --><td>12</td><td>1.0%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-3.2.html">Android 3.2</a></td><!-- Honeycomb --><td>13</td><td>2.2%</td></tr> <tr><td><a href="{@docRoot}sdk/android-4.0.html">Android 4.0 -<br/> Android 4.0.2</a></td> - <td rowspan="2">Ice Cream Sandwich</td><td>14</td><td>0.4%</td></tr> -<tr><td><a href="{@docRoot}sdk/android-4.0.3.html">Android 4.0.3</a></td><!-- ICS --><td>15</td><td>1.2%</td></tr> + <td rowspan="2">Ice Cream Sandwich</td><td>14</td><td>0.5%</td></tr> +<tr><td><a href="{@docRoot}sdk/android-4.0.3.html">Android 4.0.3</a></td><!-- ICS --><td>15</td><td>2.4%</td></tr> </table> -<p><em>Data collected during a 14-day period ending on March 5, 2012</em></p> +<p><em>Data collected during a 14-day period ending on April 2, 2012</em></p> <!-- <p style="font-size:.9em">* <em>Other: 0.1% of devices running obsolete versions</em></p> --> @@ -108,9 +108,9 @@ Google Play within a 14-day period ending on the date indicated on the x-axis.</ <div class="dashboard-panel"> <img alt="" height="250" width="660" style="padding:5px;background:#fff" -src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A%7C09/01%7C09/15%7C10/01%7C10/15%7C11/01%7C11/15%7C12/01%7C12/15%7C01/01%7C01/15%7C02/01%7C02/15%7C03/01%7C1%3A%7C2011%7C%7C%7C%7C%7C%7C%7C%7C2012%7C%7C%7C%7C2012%7C2%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C3%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:97.9,97.9,97.7,97.6,97.5,99.4,99.4,99.2,98.9,98.8,99.2,98.9,98.9|96.9,96.9,96.6,96.6,96.5,98.6,98.6,98.5,98.3,98.2,98.6,98.4,98.4|95.1,95.2,95.1,95.4,95.2,97.2,97.3,97.3,97.2,97.2,97.6,97.5,97.6|81.8,82.7,83.3,84.4,84.6,87.0,87.7,88.1,88.7,89.2,89.9,90.3,90.8|30.6,34.1,37.8,40.8,43.5,48.4,52.4,55.2,58.2,60.1,62.0,63.7,65.2|0.0,0.0,0.0,0.0,0.0,2.0,2.3,2.6,3.5,3.6,4.0,4.1,4.3|0.0,0.0,0.0,0.0,0.0,1.0,1.2,1.3,2.0,2.2,2.6,3.0,3.2|0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.3,0.4,0.7,0.8,1.1&chm=b,c3df9b,0,1,0|b,b8dc82,1,2,0|tAndroid%202.1,608920,2,0,15,,t::-5|b,addb67,2,3,0|tAndroid%202.2,517617,3,0,15,,t::-5|b,a3db4b,3,4,0|tAndroid%202.3.3,426210,4,0,15,,t::-5|b,98dc2e,4,5,0|b,8cd41b,5,6,0|b,7ec113,6,7,0|B,6fad0c,7,8,0&chg=7,25&chdl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3.3|Android%203.1|Android%203.2|Android%204.0.3&chco=add274,a2d15a,97d13e,8bcb28,7dba1e,6ea715,5f920e,507d08" /> +src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,x,y,r&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A%7C10/01%7C10/15%7C11/01%7C11/15%7C12/01%7C12/15%7C01/01%7C01/15%7C02/01%7C02/15%7C03/01%7C03/15%7C04/01%7C1%3A%7C2011%7C%7C%7C%7C%7C%7C2012%7C%7C%7C%7C%7C%7C2012%7C2%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C3%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:97.7,97.6,97.5,99.4,99.4,99.2,98.9,98.8,99.2,98.9,98.9,99.0,98.8|96.6,96.6,96.5,98.6,98.6,98.5,98.3,98.2,98.6,98.4,98.4,98.6,98.5|95.1,95.4,95.2,97.2,97.3,97.3,97.2,97.2,97.6,97.5,97.6,97.8,97.8|83.3,84.4,84.6,87.0,87.7,88.1,88.7,89.2,89.9,90.3,90.8,91.4,91.8|37.8,40.8,43.5,48.4,52.4,55.2,58.2,60.1,62.0,63.7,65.2,66.8,68.6|0.0,0.0,0.0,2.0,2.3,2.6,3.5,3.6,4.0,4.1,4.3,4.6,5.5|0.0,0.0,0.0,1.0,1.2,1.3,2.0,2.2,2.6,3.0,3.2,3.5,4.5|0.0,0.0,0.0,0.0,0.0,0.0,0.3,0.4,0.7,0.8,1.1,1.3,2.3&chm=b,c3df9b,0,1,0|b,b8dc82,1,2,0|tAndroid%202.1,608920,2,0,15,,t::-5|b,addb67,2,3,0|tAndroid%202.2,517617,3,0,15,,t::-5|b,a3db4b,3,4,0|tAndroid%202.3.3,426210,4,0,15,,t::-5|b,98dc2e,4,5,0|b,8cd41b,5,6,0|b,7ec113,6,7,0|B,6fad0c,7,8,0&chg=7,25&chdl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3.3|Android%203.1|Android%203.2|Android%204.0.3&chco=add274,a2d15a,97d13e,8bcb28,7dba1e,6ea715,5f920e,507d08" /> -<p><em>Last historical dataset collected during a 14-day period ending on March 5, 2012</em></p> +<p><em>Last historical dataset collected during a 14-day period ending on April 2, 2012</em></p> </div><!-- end dashboard-panel --> diff --git a/docs/html/resources/dashboard/screens.jd b/docs/html/resources/dashboard/screens.jd index e9c738e0e4b2..e5c79a125d33 100644 --- a/docs/html/resources/dashboard/screens.jd +++ b/docs/html/resources/dashboard/screens.jd @@ -60,7 +60,7 @@ ending on the data collection date noted below.</p> <div class="dashboard-panel"> <img alt="" width="400" height="250" -src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=Xlarge%20/%20mdpi|Large%20/%20ldpi|Large%20/%20mdpi|Normal%20/%20hdpi|Normal%20/%20ldpi|Normal%20/%20mdpi|Normal%20/%20xhdpi|Small%20/%20hdpi|Small%20/%20ldpi&chd=t%3A4.9,0.2,2.8,66.4,0.7,18.5,2.5,2.4,1.7" /> +src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl=Xlarge%20/%20mdpi|Large%20/%20ldpi|Large%20/%20mdpi|Normal%20/%20hdpi|Normal%20/%20ldpi|Normal%20/%20mdpi|Normal%20/%20xhdpi|Small%20/%20hdpi|Small%20/%20ldpi&chd=t%3A5.8,0.2,2.3,64.6,0.7,19.6,2.4,2.5,1.9" /> <table> <tr> @@ -71,31 +71,31 @@ src="http://chart.googleapis.com/chart?cht=p&chs=400x250&chco=c4df9b,6fad0c&chl= <th scope="col">xhdpi</th> </tr> <tr><th scope="row">small</th> -<td>1.7%</td> <!-- small/ldpi --> +<td>1.9%</td> <!-- small/ldpi --> <td></td> <!-- small/mdpi --> -<td>2.4%</td> <!-- small/hdpi --> +<td>2.5%</td> <!-- small/hdpi --> <td></td> <!-- small/xhdpi --> </tr> <tr><th scope="row">normal</th> <td>0.7%</td> <!-- normal/ldpi --> -<td>18.5%</td> <!-- normal/mdpi --> -<td>66.3%</td> <!-- normal/hdpi --> -<td>2.5%</td> <!-- normal/xhdpi --> +<td>19.6%</td> <!-- normal/mdpi --> +<td>64.6%</td> <!-- normal/hdpi --> +<td>2.4%</td> <!-- normal/xhdpi --> </tr> <tr><th scope="row">large</th> <td>0.2%</td> <!-- large/ldpi --> -<td>2.8%</td> <!-- large/mdpi --> +<td>2.3%</td> <!-- large/mdpi --> <td></td> <!-- large/hdpi --> <td></td> <!-- large/xhdpi --> </tr> <tr><th scope="row">xlarge</th> <td></td> <!-- xlarge/ldpi --> -<td>4.9%</td> <!-- xlarge/mdpi --> +<td>5.8%</td> <!-- xlarge/mdpi --> <td></td> <!-- xlarge/hdpi --> <td></td> <!-- xlarge/xhdpi --> </tr> </table> -<p><em>Data collected during a 7-day period ending on March 4, 2012</em></p> +<p><em>Data collected during a 7-day period ending on April 2, 2012</em></p> </div> diff --git a/docs/html/resources/resources-data.js b/docs/html/resources/resources-data.js index 8ad970ba7c45..fb4225dc8a2d 100644 --- a/docs/html/resources/resources-data.js +++ b/docs/html/resources/resources-data.js @@ -263,6 +263,17 @@ var ANDROID_RESOURCES = [ } }, { + tags: ['article', 'input', 'ui'], + path: 'articles/spell-checker-framework.html', + title: { + en: 'The Android Spell Checker Framework' + }, + description: { + en: 'This article describes the Android spell checker framework and how to use to implement spell checking in applications.' + } + }, + + { tags: ['article', 'ui'], path: 'articles/touch-mode.html', title: { @@ -548,6 +559,16 @@ var ANDROID_RESOURCES = [ } }, { + tags: ['sample', 'new'], + path: 'samples/KeyChainDemo/index.html', + title: { + en: 'KeyChain Demo' + }, + description: { + en: 'A demo application to demonstrate how to use KeyChain APIs.' + } + }, + { tags: ['sample', 'gamedev', 'media'], path: 'samples/LunarLander/index.html', title: { diff --git a/docs/html/resources/samples/images/KeyChainDemo1.png b/docs/html/resources/samples/images/KeyChainDemo1.png Binary files differnew file mode 100644 index 000000000000..d426c225a45e --- /dev/null +++ b/docs/html/resources/samples/images/KeyChainDemo1.png diff --git a/docs/html/resources/samples/images/KeyChainDemo2.png b/docs/html/resources/samples/images/KeyChainDemo2.png Binary files differnew file mode 100755 index 000000000000..e181e5877a5e --- /dev/null +++ b/docs/html/resources/samples/images/KeyChainDemo2.png diff --git a/docs/html/resources/samples/images/KeyChainDemo3.png b/docs/html/resources/samples/images/KeyChainDemo3.png Binary files differnew file mode 100755 index 000000000000..acfdd89f1a07 --- /dev/null +++ b/docs/html/resources/samples/images/KeyChainDemo3.png diff --git a/docs/html/resources/samples/images/KeyChainDemo4.png b/docs/html/resources/samples/images/KeyChainDemo4.png Binary files differnew file mode 100755 index 000000000000..a9101abaee61 --- /dev/null +++ b/docs/html/resources/samples/images/KeyChainDemo4.png diff --git a/docs/html/sdk/android-4.0.3.jd b/docs/html/sdk/android-4.0.3.jd index c8563ac2d69a..f6dbee066a6e 100644 --- a/docs/html/sdk/android-4.0.3.jd +++ b/docs/html/sdk/android-4.0.3.jd @@ -68,6 +68,28 @@ the Android 4.0.x system components will not be available for download.</p> <p><a href="#" onclick="return toggleContent(this)"> <img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-content-img" alt="" /> + Revision 3</a> <em>(March 2012)</em> + </a></p> + + <div class="toggle-content-toggleme" style="padding-left:2em;"> + + <p>Maintenance update. The system version is 4.0.4.</p> + <p class="note"><strong>Note:</strong> This system image includes support for emulator +hardware graphics acceleration when used with SDK Tools r17 or higher. +(<a href="{@docRoot}guide/developing/devices/emulator.html#accel-graphics">more info</a>)</p> + <dl> + <dt>Dependencies:</dt> + <dd>SDK Tools r17 or higher is required.</dd> + </dl> + + </div> +</div> + +<div class="toggle-content closed" style="padding-left:1em;"> + + <p><a href="#" onclick="return toggleContent(this)"> + <img src="{@docRoot}assets/images/triangle-closed.png" +class="toggle-content-img" alt="" /> Revision 2</a> <em>(January 2012)</em> </a></p> diff --git a/docs/html/sdk/eclipse-adt.jd b/docs/html/sdk/eclipse-adt.jd index 485f01e50f39..e117118b8d04 100644 --- a/docs/html/sdk/eclipse-adt.jd +++ b/docs/html/sdk/eclipse-adt.jd @@ -1,8 +1,8 @@ page.title=ADT Plugin for Eclipse -adt.zip.version=17.0.0 -adt.zip.download=ADT-17.0.0.zip -adt.zip.bytes=12836115 -adt.zip.checksum=ecb12c07e534997cd32c66d57f21b770 +adt.zip.version=18.0.0 +adt.zip.download=ADT-18.0.0.zip +adt.zip.bytes=12834793 +adt.zip.checksum=b446fa157ed97af79d1e21629201efbb @jd:body @@ -108,10 +108,45 @@ padding: .25em 1em; } </style> + <div class="toggleable opened"> <a href="#" onclick="return toggleDiv(this)"> <img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-img" height="9px" width="9px" /> +ADT 18.0.0</a> <em>(April 2012)</em> + <div class="toggleme"> +<dl> + <dt>Dependencies:</dt> + + <dd> + <ul> + <li>Java 1.6 or higher is required for ADT 18.0.0.</li> + <li>Eclipse Helios (Version 3.6.2) or higher is required for ADT 18.0.0.</li> + <li>ADT 18.0.0 is designed for use with <a href="{@docRoot}sdk/tools-notes.html">SDK Tools + r18</a>. If you haven't already installed SDK Tools r18 into your SDK, use the Android SDK + Manager to do so.</li> + </ul> + </dd> + + <dt>Bug fixes:</dt> + <dd> + <ul> + <li>Fixed problem where exporting release package does not recompile libraries in release + mode. + (<a href="http://code.google.com/p/android/issues/detail?id=27940">Issue 27940</a>)</li> + </ul> + </dd> + +</dl> + +</div> +</div> + + +<div class="toggleable closed"> + <a href="#" onclick="return toggleDiv(this)"> + <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-img" height="9px" +width="9px" /> ADT 17.0.0</a> <em>(March 2012)</em> <div class="toggleme"> <dl> diff --git a/docs/html/sdk/index.jd b/docs/html/sdk/index.jd index 175ab50a739f..b56ccdbd83f0 100644 --- a/docs/html/sdk/index.jd +++ b/docs/html/sdk/index.jd @@ -2,21 +2,21 @@ page.title=Android SDK page.metaDescription=Download the official Android SDK to develop apps for Android-powered devices. sdk.redirect=0 -sdk.win_installer=installer_r17-windows.exe -sdk.win_installer_bytes=37410775 -sdk.win_installer_checksum=5afaf6511ebaa52bd6d1dba4afc61e41 +sdk.win_installer=installer_r18-windows.exe +sdk.win_installer_bytes=37456234 +sdk.win_installer_checksum=48b1fe7b431afe6b9c8a992bf75dd898 -sdk.win_download=android-sdk_r17-windows.zip -sdk.win_bytes=37417953 -sdk.win_checksum=3af1baeb39707e54df068e939aea5a79 +sdk.win_download=android-sdk_r18-windows.zip +sdk.win_bytes=37448775 +sdk.win_checksum=bfbfdf8b2d0fdecc2a621544d706fa98 -sdk.mac_download=android-sdk_r17-macosx.zip -sdk.mac_bytes=33867836 -sdk.mac_checksum=52639aae036b7c2e47cf291696b23236 +sdk.mac_download=android-sdk_r18-macosx.zip +sdk.mac_bytes=33903758 +sdk.mac_checksum=8328e8a5531c9d6f6f1a0261cb97af36 -sdk.linux_download=android-sdk_r17-linux.tgz -sdk.linux_bytes=29706368 -sdk.linux_checksum=14e99dfa8eb1a8fadd2f3557322245c4 +sdk.linux_download=android-sdk_r18-linux.tgz +sdk.linux_bytes=29731463 +sdk.linux_checksum=6cd716d0e04624b865ffed3c25b3485c @jd:body diff --git a/docs/html/sdk/ndk/index.jd b/docs/html/sdk/ndk/index.jd index a1c59e3ab0ff..6f06de36e5ac 100644 --- a/docs/html/sdk/ndk/index.jd +++ b/docs/html/sdk/ndk/index.jd @@ -1,16 +1,16 @@ ndk=true -ndk.win_download=android-ndk-r7b-windows.zip -ndk.win_bytes=80346206 -ndk.win_checksum=c42b0c9c14428397337421d5e4999380 +ndk.win_download=android-ndk-r7c-windows.zip +ndk.win_bytes=80361003 +ndk.win_checksum=e86184cdc4bf71d32fa9185fad8544e2 -ndk.mac_download=android-ndk-r7b-darwin-x86.tar.bz2 -ndk.mac_bytes=73817184 -ndk.mac_checksum=6daa82ca6b73bc0614c9997430079c7a +ndk.mac_download=android-ndk-r7c-darwin-x86.tar.bz2 +ndk.mac_bytes=73836512 +ndk.mac_checksum=025f57feb5f32ed993a5fa7f5996477d -ndk.linux_download=android-ndk-r7b-linux-x86.tar.bz2 -ndk.linux_bytes=64349733 -ndk.linux_checksum=0eb8af18796cdaa082df8f7c54ad7f9a +ndk.linux_download=android-ndk-r7c-linux-x86.tar.bz2 +ndk.linux_bytes=63432410 +ndk.linux_checksum=0bc21b78823dcf6f86b988203626b1fe page.title=Android NDK @@ -62,6 +62,59 @@ padding: .25em 1em; <div class="toggleable open"> <a href="#" onclick="return toggleDiv(this)"><img src= "{@docRoot}assets/images/triangle-opened.png" class="toggle-img" height="9px" width="9px"> + Android NDK, Revision 7c</a> <em>(March 2012)</em> + + <div class="toggleme"> + <p>This release of the NDK includes an important fix for Tegra2-based devices, and a few +additional fixes and improvements:</p> + + <dl> + <dt>Important bug fixes:</dt> + + <dd> + <ul> + <li>Fixed GNU STL armeabi-v7a binaries to not crash on non-NEON + devices. The files provided with NDK r7b were not configured properly, + resulting in crashes on Tegra2-based devices and others when trying to use + certain floating-point functions (e.g., {@code cosf}, {@code sinf}, {@code expf}).</li> + </ul> + </dd> + + <dt>Important changes:</dt> + + <dd> + <ul> + <li>Added support for custom output directories through the {@code NDK_OUT} + environment variable. When defined, this variable is used to store all + intermediate generated files, instead of {@code $PROJECT_PATH/obj}. The variable is + also recognized by {@code ndk-gdb}. </li> + <li>Added support for building modules with hundreds or even thousands of source + files by defining {@code LOCAL_SHORT_COMMANDS} to {@code true} in your {@code Android.mk}. + <p>This change forces the NDK build system to put most linker or archiver options + into list files, as a work-around for command-line length limitations. + See {@code docs/ANDROID-MK.html} for details.</p> + </li> + </ul> + </dd> + + <dt>Other bug fixes:</dt> + + <dd> + <ul> + <li>Fixed {@code android_getCpuCount()} implementation in the {@code cpufeatures} +helper library. On certain devices, where cores are enabled dynamically by the system, the previous +implementation would report the total number of <em>active</em> cores the first time the function +was called, rather than the total number of <em>physically available</em> cores.</li> + </ul> + </dd> + </dl> + </div> +</div> + + +<div class="toggleable closed"> + <a href="#" onclick="return toggleDiv(this)"><img src= + "{@docRoot}assets/images/triangle-closed.png" class="toggle-img" height="9px" width="9px"> Android NDK, Revision 7b</a> <em>(February 2012)</em> <div class="toggleme"> diff --git a/docs/html/sdk/sdk_toc.cs b/docs/html/sdk/sdk_toc.cs index 5413784cba96..3aafea9e9c28 100644 --- a/docs/html/sdk/sdk_toc.cs +++ b/docs/html/sdk/sdk_toc.cs @@ -40,8 +40,7 @@ <li> <span class="heading">Android 3.0 Preview SDK</span> <ul> - <li><a href="<?cs var:toroot ?>sdk/preview/start.html">Getting Started</a> <span -class="new">new!</span></li> + <li><a href="<?cs var:toroot ?>sdk/preview/start.html">Getting Started</a></li> </ul> </li><?cs /if ?> @@ -79,10 +78,9 @@ class="new">new!</span></li> <ul> <li class="toggle-list"> <div><a href="<?cs var:toroot ?>sdk/android-4.0-highlights.html"> - <span class="en">Android 4.0.x Platform</span></a> <span class="new">new!</span></div> + <span class="en">Android 4.0.x Platform</span></a></div> <ul> - <li><a href="<?cs var:toroot ?>sdk/android-4.0.3.html">Android 4.0.3 Platform</a> - <span class="new">new!</span></li> + <li><a href="<?cs var:toroot ?>sdk/android-4.0.3.html">Android 4.0.3 Platform</a></li> <li><a href="<?cs var:toroot ?>sdk/android-4.0.html">Android 4.0 Platform</a> </li> </ul> </li> @@ -153,11 +151,9 @@ class="new">new!</span></li> </li> </ul> <ul> - <li><a href="<?cs var:toroot ?>sdk/tools-notes.html">SDK Tools, r17</a> <span -class="new">new!</span></li> + <li><a href="<?cs var:toroot ?>sdk/tools-notes.html">SDK Tools, r18</a></li> <li><a href="<?cs var:toroot ?>sdk/win-usb.html">Google USB Driver, r4</a></li> - <li><a href="<?cs var:toroot ?>sdk/compatibility-library.html">Support Package, r7</a> - <span class="new">new!</span></li> + <li><a href="<?cs var:toroot ?>sdk/compatibility-library.html">Support Package, r7</a></li> </ul> </li> <li> @@ -172,15 +168,14 @@ class="new">new!</span></li> <span style="display:none" class="zh-TW"></span> </span> <ul> - <li><a href="<?cs var:toroot ?>sdk/eclipse-adt.html">ADT 17.0.0 + <li><a href="<?cs var:toroot ?>sdk/eclipse-adt.html">ADT 18.0.0 <span style="display:none" class="de"></span> <span style="display:none" class="es"></span> <span style="display:none" class="fr"></span> <span style="display:none" class="it"></span> <span style="display:none" class="ja"></span> <span style="display:none" class="zh-CN"></span> - <span style="display:none" class="zh-TW"></span></a> <span -class="new">new!</span> + <span style="display:none" class="zh-TW"></span></a> </li> </ul> </li> @@ -196,7 +191,7 @@ class="new">new!</span> <span style="display:none" class="zh-TW"></span> </span> <ul> - <li><a href="<?cs var:toroot ?>sdk/ndk/index.html">Android NDK, r7b</a> + <li><a href="<?cs var:toroot ?>sdk/ndk/index.html">Android NDK, r7c</a> <span class="new">new!</span> </li> <li><a href="<?cs var:toroot ?>sdk/ndk/overview.html">What is the NDK?</a></li> diff --git a/docs/html/sdk/tools-notes.jd b/docs/html/sdk/tools-notes.jd index dea0c389d95e..f4e9d4d59e14 100644 --- a/docs/html/sdk/tools-notes.jd +++ b/docs/html/sdk/tools-notes.jd @@ -68,6 +68,48 @@ padding: .25em 1em; <a href="#" onclick="return toggleDiv(this)"> <img src="{@docRoot}assets/images/triangle-opened.png" class="toggle-img" height="9px" width="9px" /> + SDK Tools, Revision 18</a> <em>(April 2012)</em> + + <div class="toggleme"> + <p class="caution"><strong>Important:</strong> To download the new Android + 4.0 system components from the Android SDK Manager, you must first update the + SDK tools to revision 14 or later and restart the Android SDK Manager. If you do not, + the Android 4.0 system components will not be available for download.</p> + + <dl> + <dt>Dependencies:</dt> + <dd> + <ul> + <li>Android SDK Platform-tools revision 9 or later.</li> + <li>If you are developing in Eclipse with ADT, note that the SDK Tools r18 is designed for + use with ADT 18.0.0 and later. If you haven't already, we highly recommend updating your + <a href="{@docRoot}sdk/eclipse-adt.html">ADT Plugin</a> to 18.0.0.</li> + <li>If you are developing outside Eclipse, you must have + <a href="http://ant.apache.org/">Apache Ant</a> 1.8 or later.</li> + </ul> + </dd> + <dt>General notes:</dt> + <dd> + <ul> + <li>Updated the SdkController app to encapsulate both sensor and multitouch emulation + functionality.</li> + </ul> + </dd> + <dt>Bug fixes:</dt> + <dd> + <ul> + <li>Fixed Ant issues where some jar libraries in the {@code libs/} folder are not picked up +in some cases.</li> + </ul> + </dd> + </dl> + </div> +</div> + +<div class="toggleable closed"> + <a href="#" onclick="return toggleDiv(this)"> + <img src="{@docRoot}assets/images/triangle-closed.png" class="toggle-img" height="9px" + width="9px" /> SDK Tools, Revision 17</a> <em>(March 2012)</em> <div class="toggleme"> @@ -96,12 +138,13 @@ padding: .25em 1em; <li>Added support for hardware accelerated graphics rendering. This feature requires an API Level 15, Revision 3 or later system image. (<a href="{@docRoot}guide/developing/devices/emulator.html#accel-graphics">more info</a>) - <p class="note"><strong>Note:</strong> As of the SDK Tools Revision 17 release, the -API Level 15, Revision 3 system image is not yet available.</p> </li> <li>Added support for running Android x86 system images in virtualization mode on Windows and Mac OS X. -(<a href="{@docRoot}guide/developing/devices/emulator.html#accel-vm">more info</a>)</li> +(<a href="{@docRoot}guide/developing/devices/emulator.html#accel-vm">more info</a>) + <p class="note"><strong>Note:</strong> Use the Android SDK Manager to download and +install x86 system images. Android x86 system images are not available for all API levels.</p> + </li> <li>Added experimental support for multi-touch input by enabing the emulator to receive touch input from a USB-tethered physical Android device. (<a href="http://tools.android.com/tips/hardware-emulation">more info</a>)</li> diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java index 6b59b106c699..18a0a0c468ba 100644 --- a/graphics/java/android/renderscript/Allocation.java +++ b/graphics/java/android/renderscript/Allocation.java @@ -365,7 +365,7 @@ public class Allocation extends BaseObj { * @hide * */ - public void ioSendOutput() { + public void ioSend() { if ((mUsage & USAGE_IO_OUTPUT) == 0) { throw new RSIllegalArgumentException( "Can only send buffer if IO_OUTPUT usage specified."); @@ -375,12 +375,20 @@ public class Allocation extends BaseObj { } /** + * Delete once code is updated. + * @hide + */ + public void ioSendOutput() { + ioSend(); + } + + /** * Receive the latest input into the Allocation. * * @hide * */ - public void ioGetInput() { + public void ioReceive() { if ((mUsage & USAGE_IO_INPUT) == 0) { throw new RSIllegalArgumentException( "Can only receive if IO_INPUT usage specified."); diff --git a/include/androidfw/KeycodeLabels.h b/include/androidfw/KeycodeLabels.h index 9e4dfcb4df15..182806204b44 100755 --- a/include/androidfw/KeycodeLabels.h +++ b/include/androidfw/KeycodeLabels.h @@ -235,6 +235,14 @@ static const KeycodeLabel KEYCODES[] = { { "CALENDAR", 208 }, { "MUSIC", 209 }, { "CALCULATOR", 210 }, + { "ZENKAKU_HANKAKU", 211 }, + { "EISU", 212 }, + { "MUHENKAN", 213 }, + { "HENKAN", 214 }, + { "KATAKANA_HIRAGANA", 215 }, + { "YEN", 216 }, + { "RO", 217 }, + { "KANA", 218 }, // NOTE: If you add a new keycode here you must also add it to several other files. // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list. diff --git a/include/private/hwui/DrawGlInfo.h b/include/private/hwui/DrawGlInfo.h index abcf41df0c69..8028bf3bb3f5 100644 --- a/include/private/hwui/DrawGlInfo.h +++ b/include/private/hwui/DrawGlInfo.h @@ -44,20 +44,31 @@ struct DrawGlInfo { float dirtyBottom; /** + * Values used as the "what" parameter of the functor. + */ + enum Mode { + // Indicates that the functor is called to perform a draw + kModeDraw, + // Indicates the the functor is called only to perform + // processing and that no draw should be attempted + kModeProcess + }; + + /** * Values used by OpenGL functors to tell the framework * what to do next. */ enum Status { // The functor is done - kStatusDone, + kStatusDone = 0x0, // The functor is requesting a redraw (the clip rect // used by the redraw is specified by DrawGlInfo.) // The rest of the UI might redraw too. - kStatusDraw, + kStatusDraw = 0x1, // The functor needs to be invoked again but will // not redraw. Only the functor is invoked again // (unless another functor requests a redraw.) - kStatusInvoke + kStatusInvoke = 0x2 }; }; // struct DrawGlInfo diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index 3a3f8a586c67..f37bfd2f9c12 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -94,7 +94,8 @@ void DisplayList::outputLogBuffer(int fd) { } DisplayList::DisplayList(const DisplayListRenderer& recorder) : - mTransformMatrix(NULL), mTransformCamera(NULL), mTransformMatrix3D(NULL) { + mTransformMatrix(NULL), mTransformCamera(NULL), mTransformMatrix3D(NULL), + mStaticMatrix(NULL), mAnimationMatrix(NULL) { initFromDisplayListRenderer(recorder); } @@ -108,7 +109,6 @@ void DisplayList::initProperties() { mTop = 0; mRight = 0; mBottom = 0; - mApplicationScale = -1; mClipChildren = true; mAlpha = 1; mMultipliedAlpha = 255; @@ -143,18 +143,16 @@ void DisplayList::clearResources() { sk_free((void*) mReader.base()); if (USE_DISPLAY_LIST_PROPERTIES) { - if (mTransformMatrix) { - delete mTransformMatrix; - mTransformMatrix = NULL; - } - if (mTransformCamera) { - delete mTransformCamera; - mTransformCamera = NULL; - } - if (mTransformMatrix3D) { - delete mTransformMatrix3D; - mTransformMatrix3D = NULL; - } + delete mTransformMatrix; + delete mTransformCamera; + delete mTransformMatrix3D; + delete mStaticMatrix; + delete mAnimationMatrix; + mTransformMatrix = NULL; + mTransformCamera = NULL; + mTransformMatrix3D = NULL; + mStaticMatrix = NULL; + mAnimationMatrix = NULL; } Caches& caches = Caches::getInstance(); @@ -667,12 +665,26 @@ void DisplayList::updateMatrix() { void DisplayList::outputViewProperties(OpenGLRenderer& renderer, char* indent) { if (USE_DISPLAY_LIST_PROPERTIES) { updateMatrix(); - if (mApplicationScale >= 0) { - ALOGD("%s%s %.2f, %.2f", (char*) indent, "Scale", - mApplicationScale, mApplicationScale); - } if (mLeft != 0 || mTop != 0) { - ALOGD("%s%s %d, %d", indent, "Translate", mLeft, mTop); + ALOGD("%s%s %d, %d", indent, "Translate (left, top)", mLeft, mTop); + } + if (mStaticMatrix) { + ALOGD("%s%s %p: [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f]", + indent, "ConcatMatrix (static)", mStaticMatrix, + mStaticMatrix->get(0), mStaticMatrix->get(1), + mStaticMatrix->get(2), mStaticMatrix->get(3), + mStaticMatrix->get(4), mStaticMatrix->get(5), + mStaticMatrix->get(6), mStaticMatrix->get(7), + mStaticMatrix->get(8)); + } + if (mAnimationMatrix) { + ALOGD("%s%s %p: [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f]", + indent, "ConcatMatrix (animation)", mAnimationMatrix, + mAnimationMatrix->get(0), mAnimationMatrix->get(1), + mAnimationMatrix->get(2), mAnimationMatrix->get(3), + mAnimationMatrix->get(4), mAnimationMatrix->get(5), + mAnimationMatrix->get(6), mAnimationMatrix->get(7), + mAnimationMatrix->get(8)); } if (mMatrixFlags != 0) { if (mMatrixFlags == TRANSLATION) { @@ -719,13 +731,29 @@ void DisplayList::setViewProperties(OpenGLRenderer& renderer, uint32_t width, ui #endif updateMatrix(); if (mLeft != 0 || mTop != 0) { - DISPLAY_LIST_LOGD("%s%s %d, %d", indent, "Translate", mLeft, mTop); + DISPLAY_LIST_LOGD("%s%s %d, %d", indent, "Translate (left, top)", mLeft, mTop); renderer.translate(mLeft, mTop); } - if (mApplicationScale >= 0) { - DISPLAY_LIST_LOGD("%s%s %.2f, %.2f", (char*) indent, "Scale", - mApplicationScale, mApplicationScale); - renderer.scale(mApplicationScale, mApplicationScale); + if (mStaticMatrix) { + DISPLAY_LIST_LOGD( + "%s%s %p: [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f]", + indent, "ConcatMatrix (static)", mStaticMatrix, + mStaticMatrix->get(0), mStaticMatrix->get(1), + mStaticMatrix->get(2), mStaticMatrix->get(3), + mStaticMatrix->get(4), mStaticMatrix->get(5), + mStaticMatrix->get(6), mStaticMatrix->get(7), + mStaticMatrix->get(8)); + renderer.concatMatrix(mStaticMatrix); + } else if (mAnimationMatrix) { + DISPLAY_LIST_LOGD( + "%s%s %p: [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f] [%.2f, %.2f, %.2f]", + indent, "ConcatMatrix (animation)", mAnimationMatrix, + mAnimationMatrix->get(0), mAnimationMatrix->get(1), + mAnimationMatrix->get(2), mAnimationMatrix->get(3), + mAnimationMatrix->get(4), mAnimationMatrix->get(5), + mAnimationMatrix->get(6), mAnimationMatrix->get(7), + mAnimationMatrix->get(8)); + renderer.concatMatrix(mAnimationMatrix); } if (mMatrixFlags != 0) { if (mMatrixFlags == TRANSLATION) { diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index fff1d7c64bbd..38b0a6d6f173 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -51,7 +51,7 @@ namespace uirenderer { // Set to 1 to enable native processing of View properties. 0 by default. Eventually this // will go away and we will always use this approach for accelerated apps. -#define USE_DISPLAY_LIST_PROPERTIES 0 +#define USE_DISPLAY_LIST_PROPERTIES 1 #define TRANSLATION 0x0001 #define ROTATION 0x0002 @@ -156,14 +156,24 @@ public: } } - void setApplicationScale(float scale) { - mApplicationScale = scale; - } - void setClipChildren(bool clipChildren) { mClipChildren = clipChildren; } + void setStaticMatrix(SkMatrix* matrix) { + delete mStaticMatrix; + mStaticMatrix = new SkMatrix(*matrix); + } + + void setAnimationMatrix(SkMatrix* matrix) { + delete mAnimationMatrix; + if (matrix) { + mAnimationMatrix = new SkMatrix(*matrix); + } else { + mAnimationMatrix = NULL; + } + } + void setAlpha(float alpha) { if (alpha != mAlpha) { mAlpha = alpha; @@ -483,7 +493,6 @@ private: String8 mName; // View properties - float mApplicationScale; bool mClipChildren; float mAlpha; int mMultipliedAlpha; @@ -502,6 +511,8 @@ private: SkMatrix* mTransformMatrix; Sk3DView* mTransformCamera; SkMatrix* mTransformMatrix3D; + SkMatrix* mStaticMatrix; + SkMatrix* mAnimationMatrix; bool mCaching; }; diff --git a/libs/hwui/LayerCache.cpp b/libs/hwui/LayerCache.cpp index d304b374850e..eea707edb457 100644 --- a/libs/hwui/LayerCache.cpp +++ b/libs/hwui/LayerCache.cpp @@ -69,10 +69,14 @@ void LayerCache::setMaxSize(uint32_t maxSize) { void LayerCache::deleteLayer(Layer* layer) { if (layer) { - LAYER_LOGD("Destroying layer %dx%d", layer->getWidth(), layer->getHeight()); + GLuint fbo = layer->getFbo(); + LAYER_LOGD("Destroying layer %dx%d, fbo %d", layer->getWidth(), layer->getHeight(), fbo); + mSize -= layer->getWidth() * layer->getHeight() * 4; - layer->deleteFbo(); + + if (fbo) Caches::getInstance().fboCache.put(fbo); layer->deleteTexture(); + delete layer; } } @@ -174,6 +178,10 @@ bool LayerCache::put(Layer* layer) { victim->layer.getHeight()); } + layer->deferredUpdateScheduled = false; + layer->renderer = NULL; + layer->displayList = NULL; + LayerEntry entry(layer); mCache.add(entry); diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp index e320eb2e0047..2a4e72be0e31 100644 --- a/libs/hwui/LayerRenderer.cpp +++ b/libs/hwui/LayerRenderer.cpp @@ -309,6 +309,7 @@ void LayerRenderer::destroyLayer(Layer* layer) { if (fbo) { flushLayer(layer); Caches::getInstance().fboCache.put(fbo); + layer->setFbo(0); } if (!Caches::getInstance().layerCache.put(layer)) { diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index eb4b83bfb017..115787c87ebc 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -166,6 +166,7 @@ void OpenGLRenderer::prepare(bool opaque) { void OpenGLRenderer::prepareDirty(float left, float top, float right, float bottom, bool opaque) { mCaches.clearGarbage(); + mFunctors.clear(); mSnapshot = new Snapshot(mFirstSnapshot, SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); @@ -236,7 +237,39 @@ void OpenGLRenderer::resume() { glBlendEquation(GL_FUNC_ADD); } -status_t OpenGLRenderer::callDrawGLFunction(Functor *functor, Rect& dirty) { +status_t OpenGLRenderer::invokeFunctors(Rect& dirty) { + status_t result = DrawGlInfo::kStatusDone; + + Vector<Functor*> functors(mFunctors); + mFunctors.clear(); + + DrawGlInfo info; + info.clipLeft = 0; + info.clipTop = 0; + info.clipRight = 0; + info.clipBottom = 0; + info.isLayer = false; + memset(info.transform, 0, sizeof(float) * 16); + + size_t count = functors.size(); + for (size_t i = 0; i < count; i++) { + Functor* f = functors.itemAt(i); + result |= (*f)(DrawGlInfo::kModeProcess, &info); + + if (result != DrawGlInfo::kStatusDone) { + Rect localDirty(info.dirtyLeft, info.dirtyTop, info.dirtyRight, info.dirtyBottom); + dirty.unionWith(localDirty); + + if (result == DrawGlInfo::kStatusInvoke) { + mFunctors.push(f); + } + } + } + + return result; +} + +status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) { interrupt(); if (mDirtyClip) { setScissorFromClip(); @@ -261,11 +294,15 @@ status_t OpenGLRenderer::callDrawGLFunction(Functor *functor, Rect& dirty) { info.isLayer = hasLayer(); getSnapshot()->transform->copyTo(&info.transform[0]); - status_t result = (*functor)(0, &info); + status_t result = (*functor)(DrawGlInfo::kModeDraw, &info); - if (result != 0) { + if (result != DrawGlInfo::kStatusDone) { Rect localDirty(info.dirtyLeft, info.dirtyTop, info.dirtyRight, info.dirtyBottom); dirty.unionWith(localDirty); + + if (result == DrawGlInfo::kStatusInvoke) { + mFunctors.push(functor); + } } resume(); @@ -641,6 +678,7 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) { // Put the FBO name back in the cache, if it doesn't fit, it will be destroyed mCaches.fboCache.put(current->fbo); + layer->setFbo(0); } dirtyClip(); diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index 3ba6202b82ea..b6519047eead 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -72,7 +72,8 @@ public: virtual void interrupt(); virtual void resume(); - virtual status_t callDrawGLFunction(Functor *functor, Rect& dirty); + ANDROID_API status_t invokeFunctors(Rect& dirty); + virtual status_t callDrawGLFunction(Functor* functor, Rect& dirty); ANDROID_API int getSaveCount() const; virtual int save(int flags); @@ -602,8 +603,10 @@ private: // Various caches Caches& mCaches; - // List of rectagnles to clear after saveLayer() is invoked + // List of rectangles to clear after saveLayer() is invoked Vector<Rect*> mLayers; + // List of functors to invoke after a frame is drawn + Vector<Functor*> mFunctors; // Indentity matrix const mat4 mIdentity; diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 5cc24c003928..4e112af8f8ce 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -519,13 +519,34 @@ public class AudioRecord // start recording synchronized(mRecordingStateLock) { - if (native_start() == SUCCESS) { + if (native_start(MediaSyncEvent.SYNC_EVENT_NONE, 0) == SUCCESS) { mRecordingState = RECORDSTATE_RECORDING; } } } + /** + * Starts recording from the AudioRecord instance when the specified synchronization event + * occurs on the specified audio session. + * @throws IllegalStateException + * @param syncEvent event that triggers the capture. + * @see MediaSyncEvent + * @hide + */ + public void startRecording(MediaSyncEvent syncEvent) + throws IllegalStateException { + if (mState != STATE_INITIALIZED) { + throw(new IllegalStateException("startRecording() called on an " + +"uninitialized AudioRecord.")); + } + // start recording + synchronized(mRecordingStateLock) { + if (native_start(syncEvent.getType(), syncEvent.getAudioSessionId()) == SUCCESS) { + mRecordingState = RECORDSTATE_RECORDING; + } + } + } /** * Stops recording. @@ -787,7 +808,7 @@ public class AudioRecord private native final void native_release(); - private native final int native_start(); + private native final int native_start(int syncEvent, int sessionId); private native final void native_stop(); diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index b5e832c45158..18a00bca0f92 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -311,6 +311,10 @@ public class AudioSystem public static final int FOR_DOCK = 3; private static final int NUM_FORCE_USE = 4; + // usage for AudioRecord.startRecordingSync(), must match AudioSystem::sync_event_t + public static final int SYNC_EVENT_NONE = 0; + public static final int SYNC_EVENT_PRESENTATION_COMPLETE = 1; + public static native int setDeviceConnectionState(int device, int state, String device_address); public static native int getDeviceConnectionState(int device, String device_address); public static native int setPhoneState(int state); diff --git a/media/java/android/media/MediaCodecList.java b/media/java/android/media/MediaCodecList.java new file mode 100644 index 000000000000..b46ce968269f --- /dev/null +++ b/media/java/android/media/MediaCodecList.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +/** + * MediaCodecList class can be used to enumerate available codecs, + * find a codec supporting a given format and query the capabilities + * of a given codec. + * @hide +*/ +final public class MediaCodecList { + public static native final int countCodecs(); + public static native final String getCodecName(int index); + public static native final boolean isEncoder(int index); + public static native final String[] getSupportedTypes(int index); + + public static final class CodecProfileLevel { + public int mProfile; + public int mLevel; + }; + + public static final class CodecCapabilities { + public CodecProfileLevel[] mProfileLevels; + public int[] mColorFormats; + }; + public static native final CodecCapabilities getCodecCapabilities( + int index, String type); + + private static native final void native_init(); + + private MediaCodecList() {} + + static { + System.loadLibrary("media_jni"); + native_init(); + } +} diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java index 7f7e284cda8e..c9bec1814278 100644 --- a/media/java/android/media/MediaFile.java +++ b/media/java/android/media/MediaFile.java @@ -179,6 +179,7 @@ public class MediaFile { if (isWMAEnabled()) { addFileType("WMA", FILE_TYPE_WMA, "audio/x-ms-wma", MtpConstants.FORMAT_WMA); } + addFileType("OGG", FILE_TYPE_OGG, "audio/ogg", MtpConstants.FORMAT_OGG); addFileType("OGG", FILE_TYPE_OGG, "application/ogg", MtpConstants.FORMAT_OGG); addFileType("OGA", FILE_TYPE_OGG, "application/ogg", MtpConstants.FORMAT_OGG); addFileType("AAC", FILE_TYPE_AAC, "audio/aac", MtpConstants.FORMAT_AAC); diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java index d92180db62d5..c38f8f35a2bf 100644 --- a/media/java/android/media/MediaPlayer.java +++ b/media/java/android/media/MediaPlayer.java @@ -1883,8 +1883,6 @@ public class MediaPlayer return; case MEDIA_ERROR: - // For PV specific error values (msg.arg2) look in - // opencore/pvmi/pvmf/include/pvmf_return_codes.h Log.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")"); boolean error_was_handled = false; if (mOnErrorListener != null) { diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index a8144a783937..2f4ed89e126e 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -75,7 +75,7 @@ import libcore.io.Libcore; * - the processDirectory() JNI method wraps the provided mediascanner client in a native * 'MyMediaScannerClient' class, then calls processDirectory() on the native MediaScanner * object (which got created when the Java MediaScanner was created). - * - native MediaScanner.processDirectory() (currently part of opencore) calls + * - native MediaScanner.processDirectory() calls * doProcessDirectory(), which recurses over the folder, and calls * native MyMediaScannerClient.scanFile() for every file whose extension matches. * - native MyMediaScannerClient.scanFile() calls back on Java MediaScannerClient.scanFile, @@ -839,7 +839,7 @@ public class MediaScanner // and EXIF local time is not less than 1 Day, otherwise MediaProvider // will use file time as taken time. time = exif.getDateTime(); - if (Math.abs(mLastModified * 1000 - time) >= 86400000) { + if (time != -1 && Math.abs(mLastModified * 1000 - time) >= 86400000) { values.put(Images.Media.DATE_TAKEN, time); } } @@ -1183,7 +1183,7 @@ public class MediaScanner static class MediaBulkDeleter { StringBuilder whereClause = new StringBuilder(); - ArrayList<String> whereArgs = new ArrayList<String>(100); + ArrayList<String> whereArgs = new ArrayList<String>(100); IContentProvider mProvider; Uri mBaseUri; diff --git a/media/java/android/media/MediaSyncEvent.java b/media/java/android/media/MediaSyncEvent.java new file mode 100644 index 000000000000..d2a0735ff860 --- /dev/null +++ b/media/java/android/media/MediaSyncEvent.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +/** + * The MediaSyncEvent class defines events that can be used to synchronize playback or capture + * actions between different players and recorders. + * <p>For instance, {@link AudioRecord#startRecording(MediaSyncEvent)} is used to start capture + * only when the playback on a particular audio session is complete. + * The audio session ID is retrieved from a player (e.g {@link MediaPlayer}, {@link AudioTrack} or + * {@link ToneGenerator}) by use of the getAudioSessionId() method. + * @hide + */ +public class MediaSyncEvent { + + /** + * No sync event specified. When used with a synchronized playback or capture method, the + * behavior is equivalent to calling the corresponding non synchronized method. + */ + public static final int SYNC_EVENT_NONE = AudioSystem.SYNC_EVENT_NONE; + + /** + * The corresponding action is triggered only when the presentation is completed + * (meaning the media has been presented to the user) on the specified session. + * A synchronization of this type requires a source audio session ID to be set via + * {@link #setAudioSessionId(int) method. + */ + public static final int SYNC_EVENT_PRESENTATION_COMPLETE = + AudioSystem.SYNC_EVENT_PRESENTATION_COMPLETE; + + + /** + * Creates a synchronization event of the sepcified type. + * + * <p>The type specifies which kind of event is monitored. + * For instance, event {@link #SYNC_EVENT_PRESENTATION_COMPLETE} corresponds to the audio being + * presented to the user on a particular audio session. + * @param type the synchronization event type. + * @return the MediaSyncEvent created. + * @throws java.lang.IllegalArgumentException + */ + public static MediaSyncEvent createEvent(int eventType) + throws IllegalArgumentException { + if (!isValidType(eventType)) { + throw (new IllegalArgumentException(eventType + + "is not a valid MediaSyncEvent type.")); + } else { + return new MediaSyncEvent(eventType); + } + } + + private final int mType; + private int mAudioSession = 0; + + private MediaSyncEvent(int eventType) { + mType = eventType; + } + + /** + * Sets the event source audio session ID. + * + * <p>The audio session ID specifies on which audio session the synchronization event should be + * monitored. + * It is mandatory for certain event types (e.g. {@link #SYNC_EVENT_PRESENTATION_COMPLETE}). + * For instance, the audio session ID can be retrieved via + * {@link MediaPlayer#getAudioSessionId()} when monitoring an event on a particular MediaPlayer. + * @param audioSessionId the audio session ID of the event source being monitored. + * @return the MediaSyncEvent the method is called on. + * @throws java.lang.IllegalArgumentException + */ + public MediaSyncEvent setAudioSessionId(int audioSessionId) + throws IllegalArgumentException { + if (audioSessionId > 0) { + mAudioSession = audioSessionId; + } else { + throw (new IllegalArgumentException(audioSessionId + " is not a valid session ID.")); + } + return this; + } + + /** + * Gets the synchronization event type. + * + * @return the synchronization event type. + */ + public int getType() { + return mType; + } + + /** + * Gets the synchronization event audio session ID. + * + * @return the synchronization audio session ID. The returned audio session ID is 0 if it has + * not been set. + */ + public int getAudioSessionId() { + return mAudioSession; + } + + private static boolean isValidType(int type) { + switch (type) { + case SYNC_EVENT_NONE: + case SYNC_EVENT_PRESENTATION_COMPLETE: + return true; + default: + return false; + } + } +} diff --git a/media/java/android/media/ToneGenerator.java b/media/java/android/media/ToneGenerator.java index d2322650be88..4907a134006d 100644 --- a/media/java/android/media/ToneGenerator.java +++ b/media/java/android/media/ToneGenerator.java @@ -875,6 +875,15 @@ public class ToneGenerator private native final void native_finalize(); + /** + * Returns the audio session ID. + * + * @return the ID of the audio session this ToneGenerator belongs to or 0 if an error + * occured. + * @hide + */ + public native final int getAudioSessionId(); + @Override protected void finalize() { native_finalize(); } diff --git a/media/jni/Android.mk b/media/jni/Android.mk index fcc3b13170e3..dd1e505b1ffb 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -3,6 +3,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ android_media_MediaCodec.cpp \ + android_media_MediaCodecList.cpp \ android_media_MediaExtractor.cpp \ android_media_MediaPlayer.cpp \ android_media_MediaRecorder.cpp \ diff --git a/media/jni/android_media_MediaCodecList.cpp b/media/jni/android_media_MediaCodecList.cpp new file mode 100644 index 000000000000..2b8f91ec4d45 --- /dev/null +++ b/media/jni/android_media_MediaCodecList.cpp @@ -0,0 +1,190 @@ +/* + * Copyright 2012, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "MediaCodec-JNI" +#include <utils/Log.h> + +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/MediaCodecList.h> + +#include "android_runtime/AndroidRuntime.h" +#include "jni.h" +#include "JNIHelp.h" + +using namespace android; + +static jint android_media_MediaCodecList_countCodecs( + JNIEnv *env, jobject thiz) { + return MediaCodecList::getInstance()->countCodecs(); +} + +static jstring android_media_MediaCodecList_getCodecName( + JNIEnv *env, jobject thiz, jint index) { + const char *name = MediaCodecList::getInstance()->getCodecName(index); + + if (name == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return NULL; + } + + return env->NewStringUTF(name); +} + +static jboolean android_media_MediaCodecList_isEncoder( + JNIEnv *env, jobject thiz, jint index) { + return MediaCodecList::getInstance()->isEncoder(index); +} + +static jarray android_media_MediaCodecList_getSupportedTypes( + JNIEnv *env, jobject thiz, jint index) { + Vector<AString> types; + status_t err = + MediaCodecList::getInstance()->getSupportedTypes(index, &types); + + if (err != OK) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return NULL; + } + + jclass clazz = env->FindClass("java/lang/String"); + CHECK(clazz != NULL); + + jobjectArray array = env->NewObjectArray(types.size(), clazz, NULL); + + for (size_t i = 0; i < types.size(); ++i) { + jstring obj = env->NewStringUTF(types.itemAt(i).c_str()); + env->SetObjectArrayElement(array, i, obj); + env->DeleteLocalRef(obj); + obj = NULL; + } + + return array; +} + +static jobject android_media_MediaCodecList_getCodecCapabilities( + JNIEnv *env, jobject thiz, jint index, jstring type) { + if (type == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return NULL; + } + + const char *typeStr = env->GetStringUTFChars(type, NULL); + + if (typeStr == NULL) { + // Out of memory exception already pending. + return NULL; + } + + Vector<MediaCodecList::ProfileLevel> profileLevels; + Vector<uint32_t> colorFormats; + + status_t err = + MediaCodecList::getInstance()->getCodecCapabilities( + index, typeStr, &profileLevels, &colorFormats); + + env->ReleaseStringUTFChars(type, typeStr); + typeStr = NULL; + + if (err != OK) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return NULL; + } + + jclass capsClazz = + env->FindClass("android/media/MediaCodecList$CodecCapabilities"); + CHECK(capsClazz != NULL); + + jobject caps = env->AllocObject(capsClazz); + + jclass profileLevelClazz = + env->FindClass("android/media/MediaCodecList$CodecProfileLevel"); + CHECK(profileLevelClazz != NULL); + + jobjectArray profileLevelArray = + env->NewObjectArray(profileLevels.size(), profileLevelClazz, NULL); + + jfieldID profileField = + env->GetFieldID(profileLevelClazz, "mProfile", "I"); + + jfieldID levelField = + env->GetFieldID(profileLevelClazz, "mLevel", "I"); + + for (size_t i = 0; i < profileLevels.size(); ++i) { + const MediaCodecList::ProfileLevel &src = profileLevels.itemAt(i); + + jobject profileLevelObj = env->AllocObject(profileLevelClazz); + + env->SetIntField(profileLevelObj, profileField, src.mProfile); + env->SetIntField(profileLevelObj, levelField, src.mLevel); + + env->SetObjectArrayElement(profileLevelArray, i, profileLevelObj); + + env->DeleteLocalRef(profileLevelObj); + profileLevelObj = NULL; + } + + jfieldID profileLevelsField = env->GetFieldID( + capsClazz, + "mProfileLevels", + "[Landroid/media/MediaCodecList$CodecProfileLevel;"); + + env->SetObjectField(caps, profileLevelsField, profileLevelArray); + + env->DeleteLocalRef(profileLevelArray); + profileLevelArray = NULL; + + jintArray colorFormatsArray = env->NewIntArray(colorFormats.size()); + + for (size_t i = 0; i < colorFormats.size(); ++i) { + jint val = colorFormats.itemAt(i); + env->SetIntArrayRegion(colorFormatsArray, i, 1, &val); + } + + jfieldID colorFormatsField = env->GetFieldID( + capsClazz, "mColorFormats", "[I"); + + env->SetObjectField(caps, colorFormatsField, colorFormatsArray); + + env->DeleteLocalRef(colorFormatsArray); + colorFormatsArray = NULL; + + return caps; +} + +static void android_media_MediaCodecList_native_init(JNIEnv *env) { +} + +static JNINativeMethod gMethods[] = { + { "countCodecs", "()I", (void *)android_media_MediaCodecList_countCodecs }, + { "getCodecName", "(I)Ljava/lang/String;", + (void *)android_media_MediaCodecList_getCodecName }, + { "isEncoder", "(I)Z", (void *)android_media_MediaCodecList_isEncoder }, + { "getSupportedTypes", "(I)[Ljava/lang/String;", + (void *)android_media_MediaCodecList_getSupportedTypes }, + + { "getCodecCapabilities", + "(ILjava/lang/String;)Landroid/media/MediaCodecList$CodecCapabilities;", + (void *)android_media_MediaCodecList_getCodecCapabilities }, + + { "native_init", "()V", (void *)android_media_MediaCodecList_native_init }, +}; + +int register_android_media_MediaCodecList(JNIEnv *env) { + return AndroidRuntime::registerNativeMethods(env, + "android/media/MediaCodecList", gMethods, NELEM(gMethods)); +} + diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index 745e2538854e..3074bb17be24 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -881,6 +881,7 @@ static int register_android_media_MediaPlayer(JNIEnv *env) extern int register_android_media_MediaCodec(JNIEnv *env); extern int register_android_media_MediaExtractor(JNIEnv *env); +extern int register_android_media_MediaCodecList(JNIEnv *env); extern int register_android_media_MediaMetadataRetriever(JNIEnv *env); extern int register_android_media_MediaRecorder(JNIEnv *env); extern int register_android_media_MediaScanner(JNIEnv *env); @@ -962,6 +963,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) goto bail; } + if (register_android_media_MediaCodecList(env) < 0) { + ALOGE("ERROR: MediaCodec native registration failed"); + goto bail; + } + /* success -- return valid version number */ result = JNI_VERSION_1_4; diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java index b86804934095..c937a09317c6 100644 --- a/opengl/java/android/opengl/GLSurfaceView.java +++ b/opengl/java/android/opengl/GLSurfaceView.java @@ -779,8 +779,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback if (LOG_THREADS) { Log.i("DefaultContextFactory", "tid=" + Thread.currentThread().getId()); } - throw new RuntimeException("eglDestroyContext failed: " - + EGLLogWrapper.getErrorString(egl.eglGetError())); + EglHelper.throwEglException("eglDestroyContex", egl.eglGetError()); } } } @@ -1094,7 +1093,12 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * the context is current and bound to a surface. */ if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { - throwEglException("eglMakeCurrent"); + /* + * Could not make the context current, probably because the underlying + * SurfaceView surface has been destroyed. + */ + logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError()); + return false; } return true; @@ -1134,7 +1138,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback */ public int swap() { if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { - return mEgl.eglGetError(); + return mEgl.eglGetError(); } return EGL10.EGL_SUCCESS; } @@ -1180,14 +1184,23 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback throwEglException(function, mEgl.eglGetError()); } - private void throwEglException(String function, int error) { - String message = function + " failed: " + EGLLogWrapper.getErrorString(error); + public static void throwEglException(String function, int error) { + String message = formatEglError(function, error); if (LOG_THREADS) { - Log.e("EglHelper", "throwEglException tid=" + Thread.currentThread().getId() + " " + message); + Log.e("EglHelper", "throwEglException tid=" + Thread.currentThread().getId() + " " + + message); } throw new RuntimeException(message); } + public static void logEglErrorAsWarning(String tag, String function, int error) { + Log.w(tag, formatEglError(function, error)); + } + + public static String formatEglError(String function, int error) { + return function + " failed: " + EGLLogWrapper.getErrorString(error); + } + private WeakReference<GLSurfaceView> mGLSurfaceViewWeakRef; EGL10 mEgl; EGLDisplay mEglDisplay; @@ -1334,7 +1347,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } } - // Have we lost the surface view surface? + // Have we lost the SurfaceView surface? if ((! mHasSurface) && (! mWaitingForSurface)) { if (LOG_SURFACE) { Log.i("GLThread", "noticed surfaceView surface lost tid=" + getId()); @@ -1446,8 +1459,8 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback Log.w("GLThread", "egl createSurface"); } if (!mEglHelper.createSurface()) { - // Couldn't create a surface. Quit quietly. - break; + mSurfaceIsBad = true; + continue; } createEglSurface = false; } @@ -1502,12 +1515,10 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback break; default: // Other errors typically mean that the current surface is bad, - // probably because the surfaceview surface has been destroyed, + // probably because the SurfaceView surface has been destroyed, // but we haven't been notified yet. // Log the error to help developers understand why rendering stopped. - Log.w("GLThread", "eglSwapBuffers error: " + swapError + - ". Assume surfaceview surface is being destroyed. tid=" - + getId()); + EglHelper.logEglErrorAsWarning("GLThread", "eglSwapBuffers", swapError); mSurfaceIsBad = true; break; } diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java index 564b07bd6c05..ebed522d4eb7 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java @@ -456,6 +456,9 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener mPreloadTasksRunnable = new Runnable() { public void run() { + // If we set our visibility to INVISIBLE here, we avoid an extra call to + // onLayout later when we become visible (because onLayout is always called + // when going from GONE) if (!mShowing) { setVisibility(INVISIBLE); refreshRecentTasksList(); @@ -562,9 +565,6 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener if (!mShowing) { int action = ev.getAction() & MotionEvent.ACTION_MASK; if (action == MotionEvent.ACTION_DOWN) { - // If we set our visibility to INVISIBLE here, we avoid an extra call to - // onLayout later when we become visible (because onLayout is always called - // when going from GONE) post(mPreloadTasksRunnable); } else if (action == MotionEvent.ACTION_CANCEL) { setVisibility(GONE); @@ -583,9 +583,15 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener return false; } + public void preloadRecentTasksList() { + if (!mShowing) { + mPreloadTasksRunnable.run(); + } + } + public void clearRecentTasksList() { // Clear memory used by screenshots - if (mRecentTaskDescriptions != null) { + if (!mShowing && mRecentTaskDescriptions != null) { mRecentTasksLoader.cancelLoadingThumbnailsAndIcons(); mRecentTaskDescriptions.clear(); mListAdapter.notifyDataSetInvalidated(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 3a06127e1570..8d7afc89f673 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -19,31 +19,56 @@ package com.android.systemui.statusbar; import java.util.ArrayList; import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Build; +import android.os.Handler; import android.os.IBinder; +import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; import android.util.Slog; import android.view.Display; import android.view.IWindowManager; +import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; +import android.view.WindowManagerImpl; +import android.widget.LinearLayout; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarIconList; import com.android.internal.statusbar.StatusBarNotification; import com.android.systemui.SystemUI; +import com.android.systemui.recent.RecentsPanelView; +import com.android.systemui.recent.RecentTasksLoader; +import com.android.systemui.recent.TaskDescription; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.tablet.StatusBarPanel; import com.android.systemui.R; -public abstract class BaseStatusBar extends SystemUI implements CommandQueue.Callbacks { +public abstract class BaseStatusBar extends SystemUI implements + CommandQueue.Callbacks, RecentsPanelView.OnRecentsPanelVisibilityChangedListener { static final String TAG = "StatusBar"; private static final boolean DEBUG = false; + protected static final int MSG_OPEN_RECENTS_PANEL = 1020; + protected static final int MSG_CLOSE_RECENTS_PANEL = 1021; + protected static final int MSG_PRELOAD_RECENT_APPS = 1022; + protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023; + protected CommandQueue mCommandQueue; protected IStatusBarService mBarService; + protected H mHandler = createHandler(); + + // Recent apps + protected RecentsPanelView mRecentsPanel; + protected RecentTasksLoader mRecentTasksLoader; // UI-specific methods @@ -97,7 +122,7 @@ public abstract class BaseStatusBar extends SystemUI implements CommandQueue.Cal createAndAddWindows(); disable(switches[0]); - setSystemUiVisibility(switches[1]); + setSystemUiVisibility(switches[1], 0xffffffff); topAppWindowChanged(switches[2] != 0); // StatusBarManagerService has a back up of IME token and it's restored here. setImeWindowStatus(binders.get(0), switches[3], switches[4]); @@ -159,7 +184,143 @@ public abstract class BaseStatusBar extends SystemUI implements CommandQueue.Cal return vetoButton; } + + protected void applyLegacyRowBackground(StatusBarNotification sbn, View content) { + if (sbn.notification.contentView.getLayoutId() != + com.android.internal.R.layout.notification_template_base) { + int version = 0; + try { + ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.pkg, 0); + version = info.targetSdkVersion; + } catch (NameNotFoundException ex) { + Slog.e(TAG, "Failed looking up ApplicationInfo for " + sbn.pkg, ex); + } + if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) { + content.setBackgroundResource(R.drawable.notification_row_legacy_bg); + } else { + content.setBackgroundResource(R.drawable.notification_row_bg); + } + } + } + public void dismissIntruder() { // pass } + + @Override + public void toggleRecentApps() { + int msg = (mRecentsPanel.getVisibility() == View.VISIBLE) + ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL; + mHandler.removeMessages(msg); + mHandler.sendEmptyMessage(msg); + } + + @Override + public void preloadRecentApps() { + int msg = MSG_PRELOAD_RECENT_APPS; + mHandler.removeMessages(msg); + mHandler.sendEmptyMessage(msg); + } + + @Override + public void cancelPreloadRecentApps() { + int msg = MSG_CANCEL_PRELOAD_RECENT_APPS; + mHandler.removeMessages(msg); + mHandler.sendEmptyMessage(msg); + } + + @Override + public void onRecentsPanelVisibilityChanged(boolean visible) { + } + + protected abstract WindowManager.LayoutParams getRecentsLayoutParams( + LayoutParams layoutParams); + + protected void updateRecentsPanel() { + // Recents Panel + boolean visible = false; + ArrayList<TaskDescription> recentTasksList = null; + boolean firstScreenful = false; + if (mRecentsPanel != null) { + visible = mRecentsPanel.isShowing(); + WindowManagerImpl.getDefault().removeView(mRecentsPanel); + if (visible) { + recentTasksList = mRecentsPanel.getRecentTasksList(); + firstScreenful = mRecentsPanel.getFirstScreenful(); + } + } + + // Provide RecentsPanelView with a temporary parent to allow layout params to work. + LinearLayout tmpRoot = new LinearLayout(mContext); + mRecentsPanel = (RecentsPanelView) LayoutInflater.from(mContext).inflate( + R.layout.status_bar_recent_panel, tmpRoot, false); + mRecentsPanel.setRecentTasksLoader(mRecentTasksLoader); + mRecentTasksLoader.setRecentsPanel(mRecentsPanel); + mRecentsPanel.setOnTouchListener( + new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL, mRecentsPanel)); + mRecentsPanel.setVisibility(View.GONE); + + + WindowManager.LayoutParams lp = getRecentsLayoutParams(mRecentsPanel.getLayoutParams()); + + WindowManagerImpl.getDefault().addView(mRecentsPanel, lp); + mRecentsPanel.setBar(this); + if (visible) { + mRecentsPanel.show(true, false, recentTasksList, firstScreenful); + } + + } + + protected H createHandler() { + return new H(); + } + + protected class H extends Handler { + public void handleMessage(Message m) { + switch (m.what) { + case MSG_OPEN_RECENTS_PANEL: + if (DEBUG) Slog.d(TAG, "opening recents panel"); + if (mRecentsPanel != null) { + mRecentsPanel.show(true, true); + } + break; + case MSG_CLOSE_RECENTS_PANEL: + if (DEBUG) Slog.d(TAG, "closing recents panel"); + if (mRecentsPanel != null && mRecentsPanel.isShowing()) { + mRecentsPanel.show(false, true); + } + break; + case MSG_PRELOAD_RECENT_APPS: + if (DEBUG) Slog.d(TAG, "preloading recents"); + mRecentsPanel.preloadRecentTasksList(); + break; + case MSG_CANCEL_PRELOAD_RECENT_APPS: + if (DEBUG) Slog.d(TAG, "cancel preloading recents"); + mRecentsPanel.clearRecentTasksList(); + break; + } + } + } + + public class TouchOutsideListener implements View.OnTouchListener { + private int mMsg; + private StatusBarPanel mPanel; + + public TouchOutsideListener(int msg, StatusBarPanel panel) { + mMsg = msg; + mPanel = panel; + } + + public boolean onTouch(View v, MotionEvent ev) { + final int action = ev.getAction(); + if (action == MotionEvent.ACTION_OUTSIDE + || (action == MotionEvent.ACTION_DOWN + && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { + mHandler.removeMessages(mMsg); + mHandler.sendEmptyMessage(mMsg); + return true; + } + return false; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index f8dfa8fcf6b0..f88a3cc95421 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -61,8 +61,10 @@ public class CommandQueue extends IStatusBar.Stub { private static final int MSG_SET_HARD_KEYBOARD_STATUS = 10 << MSG_SHIFT; private static final int MSG_TOGGLE_RECENT_APPS = 11 << MSG_SHIFT; + private static final int MSG_PRELOAD_RECENT_APPS = 12 << MSG_SHIFT; + private static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 13 << MSG_SHIFT; - private static final int MSG_SET_NAVIGATION_ICON_HINTS = 13 << MSG_SHIFT; + private static final int MSG_SET_NAVIGATION_ICON_HINTS = 14 << MSG_SHIFT; private StatusBarIconList mList; private Callbacks mCallbacks; @@ -87,11 +89,13 @@ public class CommandQueue extends IStatusBar.Stub { public void disable(int state); public void animateExpand(); public void animateCollapse(); - public void setSystemUiVisibility(int vis); + public void setSystemUiVisibility(int vis, int mask); public void topAppWindowChanged(boolean visible); public void setImeWindowStatus(IBinder token, int vis, int backDisposition); public void setHardKeyboardStatus(boolean available, boolean enabled); public void toggleRecentApps(); + public void preloadRecentApps(); + public void cancelPreloadRecentApps(); public void setNavigationIconHints(int hints); } @@ -161,10 +165,10 @@ public class CommandQueue extends IStatusBar.Stub { } } - public void setSystemUiVisibility(int vis) { + public void setSystemUiVisibility(int vis, int mask) { synchronized (mList) { mHandler.removeMessages(MSG_SET_SYSTEMUI_VISIBILITY); - mHandler.obtainMessage(MSG_SET_SYSTEMUI_VISIBILITY, vis, 0, null).sendToTarget(); + mHandler.obtainMessage(MSG_SET_SYSTEMUI_VISIBILITY, vis, mask, null).sendToTarget(); } } @@ -199,6 +203,20 @@ public class CommandQueue extends IStatusBar.Stub { } } + public void preloadRecentApps() { + synchronized (mList) { + mHandler.removeMessages(MSG_PRELOAD_RECENT_APPS); + mHandler.obtainMessage(MSG_PRELOAD_RECENT_APPS, 0, 0, null).sendToTarget(); + } + } + + public void cancelPreloadRecentApps() { + synchronized (mList) { + mHandler.removeMessages(MSG_CANCEL_PRELOAD_RECENT_APPS); + mHandler.obtainMessage(MSG_CANCEL_PRELOAD_RECENT_APPS, 0, 0, null).sendToTarget(); + } + } + public void setNavigationIconHints(int hints) { synchronized (mList) { mHandler.removeMessages(MSG_SET_NAVIGATION_ICON_HINTS); @@ -261,7 +279,7 @@ public class CommandQueue extends IStatusBar.Stub { } break; case MSG_SET_SYSTEMUI_VISIBILITY: - mCallbacks.setSystemUiVisibility(msg.arg1); + mCallbacks.setSystemUiVisibility(msg.arg1, msg.arg2); break; case MSG_TOP_APP_WINDOW_CHANGED: mCallbacks.topAppWindowChanged(msg.arg1 != 0); @@ -275,6 +293,12 @@ public class CommandQueue extends IStatusBar.Stub { case MSG_TOGGLE_RECENT_APPS: mCallbacks.toggleRecentApps(); break; + case MSG_PRELOAD_RECENT_APPS: + mCallbacks.preloadRecentApps(); + break; + case MSG_CANCEL_PRELOAD_RECENT_APPS: + mCallbacks.cancelPreloadRecentApps(); + break; case MSG_SET_NAVIGATION_ICON_HINTS: mCallbacks.setNavigationIconHints(msg.arg1); break; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 6574c7dd71ae..5582b0f6c9e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -139,7 +139,7 @@ public class NavigationBarView extends LinearLayout { setLowProfile(false, false, false); try { - mBarService.setSystemUiVisibility(0); + mBarService.setSystemUiVisibility(0, View.SYSTEM_UI_FLAG_LOW_PROFILE); } catch (android.os.RemoteException ex) { } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 023b21f4a462..12c05ed252d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -30,24 +30,22 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; import android.content.res.Configuration; -import android.inputmethodservice.InputMethodService; +import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.Rect; -import android.graphics.drawable.Drawable; +import android.inputmethodservice.InputMethodService; import android.os.Build; import android.os.IBinder; -import android.os.RemoteException; -import android.os.Handler; import android.os.Message; +import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.provider.Settings; import android.text.TextUtils; import android.util.DisplayMetrics; -import android.util.Slog; import android.util.Log; +import android.util.Slog; import android.view.Display; import android.view.Gravity; import android.view.IWindowManager; @@ -76,19 +74,16 @@ import java.util.ArrayList; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarNotification; - import com.android.systemui.R; import com.android.systemui.SwipeHelper; import com.android.systemui.recent.RecentTasksLoader; -import com.android.systemui.recent.RecentsPanelView; -import com.android.systemui.recent.TaskDescription; -import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.BaseStatusBar; -import com.android.systemui.statusbar.StatusBarIconView; +import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.SignalClusterView; -import com.android.systemui.statusbar.policy.DateView; +import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.IntruderAlertView; +import com.android.systemui.statusbar.policy.DateView; import com.android.systemui.statusbar.policy.LocationController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NotificationRowLayout; @@ -116,8 +111,7 @@ public class PhoneStatusBar extends BaseStatusBar { private static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001; private static final int MSG_SHOW_INTRUDER = 1002; private static final int MSG_HIDE_INTRUDER = 1003; - private static final int MSG_OPEN_RECENTS_PANEL = 1020; - private static final int MSG_CLOSE_RECENTS_PANEL = 1021; + // 1020-1030 reserved for BaseStatusBar // will likely move to a resource or other tunable param at some point private static final int INTRUDER_ALERT_DECAY_MS = 0; // disabled, was 10000; @@ -152,7 +146,6 @@ public class PhoneStatusBar extends BaseStatusBar { PhoneStatusBarView mStatusBarView; int mPixelFormat; - H mHandler = new H(); Object mQueueLock = new Object(); // icons @@ -202,10 +195,6 @@ public class PhoneStatusBar extends BaseStatusBar { private View mTickerView; private boolean mTicking; - // Recent apps - private RecentsPanelView mRecentsPanel; - private RecentTasksLoader mRecentTasksLoader; - // Tracking finger for opening/closing. int mEdgeBorder; // corresponds to R.dimen.status_bar_edge_ignore boolean mTracking; @@ -382,6 +371,7 @@ public class PhoneStatusBar extends BaseStatusBar { return sb; } + @Override protected WindowManager.LayoutParams getRecentsLayoutParams(LayoutParams layoutParams) { boolean opaque = false; WindowManager.LayoutParams lp = new WindowManager.LayoutParams( @@ -406,42 +396,13 @@ public class PhoneStatusBar extends BaseStatusBar { return lp; } + @Override protected void updateRecentsPanel() { - // Recents Panel - boolean visible = false; - ArrayList<TaskDescription> recentTasksList = null; - boolean firstScreenful = false; - if (mRecentsPanel != null) { - visible = mRecentsPanel.isShowing(); - WindowManagerImpl.getDefault().removeView(mRecentsPanel); - if (visible) { - recentTasksList = mRecentsPanel.getRecentTasksList(); - firstScreenful = mRecentsPanel.getFirstScreenful(); - } - } - - // Provide RecentsPanelView with a temporary parent to allow layout params to work. - LinearLayout tmpRoot = new LinearLayout(mContext); - mRecentsPanel = (RecentsPanelView) LayoutInflater.from(mContext).inflate( - R.layout.status_bar_recent_panel, tmpRoot, false); - mRecentsPanel.setRecentTasksLoader(mRecentTasksLoader); - mRecentTasksLoader.setRecentsPanel(mRecentsPanel); - mRecentsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL, - mRecentsPanel)); - mRecentsPanel.setVisibility(View.GONE); - + super.updateRecentsPanel(); // Make .03 alpha the minimum so you always see the item a bit-- slightly below // .03, the item disappears entirely (as if alpha = 0) and that discontinuity looks // a bit jarring mRecentsPanel.setMinSwipeAlpha(0.03f); - WindowManager.LayoutParams lp = getRecentsLayoutParams(mRecentsPanel.getLayoutParams()); - - WindowManagerImpl.getDefault().addView(mRecentsPanel, lp); - mRecentsPanel.setBar(this); - if (visible) { - mRecentsPanel.show(true, false, recentTasksList, firstScreenful); - } - } protected int getStatusBarGravity() { @@ -934,24 +895,6 @@ public class PhoneStatusBar extends BaseStatusBar { return true; } - void applyLegacyRowBackground(StatusBarNotification sbn, View content) { - if (sbn.notification.contentView.getLayoutId() != - com.android.internal.R.layout.status_bar_latest_event_content) { - int version = 0; - try { - ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.pkg, 0); - version = info.targetSdkVersion; - } catch (NameNotFoundException ex) { - Slog.e(TAG, "Failed looking up ApplicationInfo for " + sbn.pkg, ex); - } - if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) { - content.setBackgroundResource(R.drawable.notification_row_legacy_bg); - } else { - content.setBackgroundResource(R.drawable.notification_row_bg); - } - } - } - StatusBarNotification removeNotificationViews(IBinder key) { NotificationData.Entry entry = mNotificationData.remove(key); if (entry == null) { @@ -1076,11 +1019,17 @@ public class PhoneStatusBar extends BaseStatusBar { } } + @Override + protected BaseStatusBar.H createHandler() { + return new PhoneStatusBar.H(); + } + /** * All changes to the status bar and notifications funnel through here and are batched. */ - private class H extends Handler { + private class H extends BaseStatusBar.H { public void handleMessage(Message m) { + super.handleMessage(m); switch (m.what) { case MSG_ANIMATE: doAnimation(); @@ -1101,18 +1050,6 @@ public class PhoneStatusBar extends BaseStatusBar { setIntruderAlertVisibility(false); mCurrentlyIntrudingNotification = null; break; - case MSG_OPEN_RECENTS_PANEL: - if (DEBUG) Slog.d(TAG, "opening recents panel"); - if (mRecentsPanel != null) { - mRecentsPanel.show(true, true); - } - break; - case MSG_CLOSE_RECENTS_PANEL: - if (DEBUG) Slog.d(TAG, "closing recents panel"); - if (mRecentsPanel != null && mRecentsPanel.isShowing()) { - mRecentsPanel.show(false, true); - } - break; } } } @@ -1516,12 +1453,13 @@ public class PhoneStatusBar extends BaseStatusBar { } @Override // CommandQueue - public void setSystemUiVisibility(int vis) { - final int old = mSystemUiVisibility; - final int diff = vis ^ old; + public void setSystemUiVisibility(int vis, int mask) { + final int oldVal = mSystemUiVisibility; + final int newVal = (oldVal&~mask) | (vis&mask); + final int diff = newVal ^ oldVal; if (diff != 0) { - mSystemUiVisibility = vis; + mSystemUiVisibility = newVal; if (0 != (diff & View.SYSTEM_UI_FLAG_LOW_PROFILE)) { final boolean lightsOut = (0 != (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE)); @@ -1540,9 +1478,9 @@ public class PhoneStatusBar extends BaseStatusBar { public void setLightsOn(boolean on) { Log.v(TAG, "setLightsOn(" + on + ")"); if (on) { - setSystemUiVisibility(mSystemUiVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE); + setSystemUiVisibility(0, View.SYSTEM_UI_FLAG_LOW_PROFILE); } else { - setSystemUiVisibility(mSystemUiVisibility | View.SYSTEM_UI_FLAG_LOW_PROFILE); + setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE, View.SYSTEM_UI_FLAG_LOW_PROFILE); } } @@ -2041,13 +1979,6 @@ public class PhoneStatusBar extends BaseStatusBar { } } - public void toggleRecentApps() { - int msg = (mRecentsPanel.getVisibility() == View.VISIBLE) - ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL; - mHandler.removeMessages(msg); - mHandler.sendEmptyMessage(msg); - } - /** * The LEDs are turned o)ff when the notification panel is shown, even just a little bit. * This was added last-minute and is inconsistent with the way the rest of the notifications @@ -2335,27 +2266,5 @@ public class PhoneStatusBar extends BaseStatusBar { vibrate(); } }; - - public class TouchOutsideListener implements View.OnTouchListener { - private int mMsg; - private RecentsPanelView mPanel; - - public TouchOutsideListener(int msg, RecentsPanelView panel) { - mMsg = msg; - mPanel = panel; - } - - public boolean onTouch(View v, MotionEvent ev) { - final int action = ev.getAction(); - if (action == MotionEvent.ACTION_OUTSIDE - || (action == MotionEvent.ACTION_DOWN - && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { - mHandler.removeMessages(mMsg); - mHandler.sendEmptyMessage(mMsg); - return true; - } - return false; - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java index 9d5faa4ed014..7325a37c8222 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java @@ -36,21 +36,19 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.content.res.Resources; -import android.inputmethodservice.InputMethodService; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; +import android.inputmethodservice.InputMethodService; import android.os.Build; -import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.text.TextUtils; import android.util.Slog; -import android.view.accessibility.AccessibilityEvent; import android.view.Display; import android.view.Gravity; import android.view.IWindowManager; @@ -62,8 +60,10 @@ import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.view.WindowManagerImpl; +import android.view.accessibility.AccessibilityEvent; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RemoteViews; @@ -78,7 +78,6 @@ import com.android.systemui.recent.RecentsPanelView; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.SignalClusterView; -import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BluetoothController; @@ -100,8 +99,7 @@ public class TabletStatusBar extends BaseStatusBar implements public static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001; public static final int MSG_OPEN_NOTIFICATION_PEEK = 1002; public static final int MSG_CLOSE_NOTIFICATION_PEEK = 1003; - public static final int MSG_OPEN_RECENTS_PANEL = 1020; - public static final int MSG_CLOSE_RECENTS_PANEL = 1021; + // 1020-1029 reserved for BaseStatusBar public static final int MSG_SHOW_CHROME = 1030; public static final int MSG_HIDE_CHROME = 1031; public static final int MSG_OPEN_INPUT_METHODS_PANEL = 1040; @@ -127,8 +125,6 @@ public class TabletStatusBar extends BaseStatusBar implements int mMenuNavIconWidth = -1; private int mMaxNotificationIcons = 5; - H mHandler = new H(); - IWindowManager mWindowManager; // tracking all current notifications @@ -189,8 +185,6 @@ public class TabletStatusBar extends BaseStatusBar implements // for disabling the status bar int mDisabled = 0; - private RecentsPanelView mRecentsPanel; - private RecentTasksLoader mRecentTasksLoader; private InputMethodsPanel mInputMethodsPanel; private CompatModePanel mCompatModePanel; @@ -348,33 +342,7 @@ public class TabletStatusBar extends BaseStatusBar implements // Recents Panel mRecentTasksLoader = new RecentTasksLoader(context); - mRecentsPanel = (RecentsPanelView) View.inflate(context, - R.layout.status_bar_recent_panel, null); - mRecentsPanel.setVisibility(View.GONE); - mRecentsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL, - mRecentsPanel)); - mRecentsPanel.setOnVisibilityChangedListener(this); - mRecentsPanel.setRecentTasksLoader(mRecentTasksLoader); - mRecentTasksLoader.setRecentsPanel(mRecentsPanel); - - lp = new WindowManager.LayoutParams( - (int) res.getDimension(R.dimen.status_bar_recents_width), - ViewGroup.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM - | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH - | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, - PixelFormat.TRANSLUCENT); - lp.gravity = Gravity.BOTTOM | Gravity.LEFT; - lp.setTitle("RecentsPanel"); - lp.windowAnimations = R.style.Animation_RecentPanel; - lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED - | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; - - WindowManagerImpl.getDefault().addView(mRecentsPanel, lp); - mRecentsPanel.setBar(this); - mRecentsPanel.setStatusBarView(mStatusBarView); + updateRecentsPanel(); // Input methods Panel mInputMethodsPanel = (InputMethodsPanel) View.inflate(context, @@ -626,7 +594,7 @@ public class TabletStatusBar extends BaseStatusBar implements mBarContents.setVisibility(View.VISIBLE); try { - mBarService.setSystemUiVisibility(View.STATUS_BAR_VISIBLE); + mBarService.setSystemUiVisibility(0, View.SYSTEM_UI_FLAG_LOW_PROFILE); } catch (RemoteException ex) { // system process dead } @@ -680,6 +648,31 @@ public class TabletStatusBar extends BaseStatusBar implements return sb; } + @Override + protected WindowManager.LayoutParams getRecentsLayoutParams(LayoutParams layoutParams) { + WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + (int) mContext.getResources().getDimension(R.dimen.status_bar_recents_width), + ViewGroup.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, + PixelFormat.TRANSLUCENT); + lp.gravity = Gravity.BOTTOM | Gravity.LEFT; + lp.setTitle("RecentsPanel"); + lp.windowAnimations = com.android.internal.R.style.Animation_RecentApplications; + lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED + | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; + + return lp; + } + + protected void updateRecentsPanel() { + super.updateRecentsPanel(); + mRecentsPanel.setStatusBarView(mStatusBarView); + } + public int getStatusBarHeight() { return mHeightReceiver.getHeight(); } @@ -702,8 +695,14 @@ public class TabletStatusBar extends BaseStatusBar implements } } - private class H extends Handler { + @Override + protected BaseStatusBar.H createHandler() { + return new TabletStatusBar.H(); + } + + private class H extends BaseStatusBar.H { public void handleMessage(Message m) { + super.handleMessage(m); switch (m.what) { case MSG_OPEN_NOTIFICATION_PEEK: if (DEBUG) Slog.d(TAG, "opening notification peek window; arg=" + m.arg1); @@ -798,18 +797,6 @@ public class TabletStatusBar extends BaseStatusBar implements mNotificationArea.setVisibility(View.VISIBLE); } break; - case MSG_OPEN_RECENTS_PANEL: - if (DEBUG) Slog.d(TAG, "opening recents panel"); - if (mRecentsPanel != null) { - mRecentsPanel.show(true, true); - } - break; - case MSG_CLOSE_RECENTS_PANEL: - if (DEBUG) Slog.d(TAG, "closing recents panel"); - if (mRecentsPanel != null && mRecentsPanel.isShowing()) { - mRecentsPanel.show(false, true); - } - break; case MSG_OPEN_INPUT_METHODS_PANEL: if (DEBUG) Slog.d(TAG, "opening input methods panel"); if (mInputMethodsPanel != null) mInputMethodsPanel.openPanel(); @@ -1178,14 +1165,20 @@ public class TabletStatusBar extends BaseStatusBar implements } @Override // CommandQueue - public void setSystemUiVisibility(int vis) { - if (vis != mSystemUiVisibility) { - mSystemUiVisibility = vis; - - mHandler.removeMessages(MSG_HIDE_CHROME); - mHandler.removeMessages(MSG_SHOW_CHROME); - mHandler.sendEmptyMessage(0 == (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) - ? MSG_SHOW_CHROME : MSG_HIDE_CHROME); + public void setSystemUiVisibility(int vis, int mask) { + final int oldVal = mSystemUiVisibility; + final int newVal = (oldVal&~mask) | (vis&mask); + final int diff = newVal ^ oldVal; + + if (diff != 0) { + mSystemUiVisibility = newVal; + + if (0 != (diff & View.SYSTEM_UI_FLAG_LOW_PROFILE)) { + mHandler.removeMessages(MSG_HIDE_CHROME); + mHandler.removeMessages(MSG_SHOW_CHROME); + mHandler.sendEmptyMessage(0 == (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) + ? MSG_SHOW_CHROME : MSG_HIDE_CHROME); + } notifyUiVisibilityChanged(); } @@ -1200,9 +1193,9 @@ public class TabletStatusBar extends BaseStatusBar implements Slog.v(TAG, "setLightsOn(" + on + ")"); if (on) { - setSystemUiVisibility(mSystemUiVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE); + setSystemUiVisibility(0, View.SYSTEM_UI_FLAG_LOW_PROFILE); } else { - setSystemUiVisibility(mSystemUiVisibility | View.SYSTEM_UI_FLAG_LOW_PROFILE); + setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE, View.SYSTEM_UI_FLAG_LOW_PROFILE); } } @@ -1893,24 +1886,6 @@ public class TabletStatusBar extends BaseStatusBar implements return true; } - void applyLegacyRowBackground(StatusBarNotification sbn, View content) { - if (sbn.notification.contentView.getLayoutId() != - com.android.internal.R.layout.status_bar_latest_event_content) { - int version = 0; - try { - ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(sbn.pkg, 0); - version = info.targetSdkVersion; - } catch (NameNotFoundException ex) { - Slog.e(TAG, "Failed looking up ApplicationInfo for " + sbn.pkg, ex); - } - if (version > 0 && version < Build.VERSION_CODES.GINGERBREAD) { - content.setBackgroundResource(R.drawable.notification_row_legacy_bg); - } else { - content.setBackgroundResource(R.drawable.notification_row_bg); - } - } - } - public void clearAll() { try { mBarService.onClearAllNotifications(); @@ -1921,13 +1896,6 @@ public class TabletStatusBar extends BaseStatusBar implements visibilityChanged(false); } - public void toggleRecentApps() { - int msg = (mRecentsPanel.getVisibility() == View.VISIBLE) - ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL; - mHandler.removeMessages(msg); - mHandler.sendEmptyMessage(msg); - } - private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -1953,28 +1921,6 @@ public class TabletStatusBar extends BaseStatusBar implements } }; - public class TouchOutsideListener implements View.OnTouchListener { - private int mMsg; - private StatusBarPanel mPanel; - - public TouchOutsideListener(int msg, StatusBarPanel panel) { - mMsg = msg; - mPanel = panel; - } - - public boolean onTouch(View v, MotionEvent ev) { - final int action = ev.getAction(); - if (action == MotionEvent.ACTION_OUTSIDE - || (action == MotionEvent.ACTION_DOWN - && !mPanel.isInContentArea((int)ev.getX(), (int)ev.getY()))) { - mHandler.removeMessages(mMsg); - mHandler.sendEmptyMessage(mMsg); - return true; - } - return false; - } - } - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.print("mDisabled=0x"); pw.println(Integer.toHexString(mDisabled)); diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index 301dbf51cbff..c3c49b0a5216 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -2829,6 +2829,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { if (mContentParent == null) { mContentParent = generateLayout(mDecor); + // Set up decor part of UI to ignore fitsSystemWindows if appropriate. + mDecor.makeOptionalFitsSystemWindows(); + mTitleView = (TextView)findViewById(com.android.internal.R.id.title); if (mTitleView != null) { if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) { diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 92c94a997023..fb1e106b32bf 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -230,6 +230,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { static public final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; static public final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey"; + /** + * These are the system UI flags that, when changing, can cause the layout + * of the screen to change. + */ + static final int SYSTEM_UI_CHANGING_LAYOUT = + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN; + // Useful scan codes. private static final int SW_LID = 0x00; private static final int BTN_MOUSE = 0x110; @@ -391,6 +398,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { // that area of the display from all other windows. int mRestrictedScreenLeft, mRestrictedScreenTop; int mRestrictedScreenWidth, mRestrictedScreenHeight; + // For applications requesting stable content insets, these are them. + int mStableLeft, mStableTop, mStableRight, mStableBottom; // During layout, the current screen borders with all outer decoration // (status bar, input method dock) accounted for. int mCurLeft, mCurTop, mCurRight, mCurBottom; @@ -430,6 +439,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { boolean mHideLockScreen; boolean mDismissKeyguard; boolean mHomePressed; + boolean mHomeLongPressed; Intent mHomeIntent; Intent mCarDockIntent; Intent mDeskDockIntent; @@ -744,7 +754,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Eat the longpress so it won't dismiss the recent apps dialog when // the user lets go of the home key - mHomePressed = false; + mHomeLongPressed = true; } if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_DIALOG) { @@ -1619,33 +1629,45 @@ public class PhoneWindowManager implements WindowManagerPolicy { // it handle it, because that gives us the correct 5 second // timeout. if (keyCode == KeyEvent.KEYCODE_HOME) { + // If we have released the home key, and didn't do anything else // while it was pressed, then it is time to go home! - if (mHomePressed && !down) { + if (!down) { + final boolean homeWasLongPressed = mHomeLongPressed; mHomePressed = false; - if (!canceled) { - // If an incoming call is ringing, HOME is totally disabled. - // (The user is already on the InCallScreen at this point, - // and his ONLY options are to answer or reject the call.) - boolean incomingRinging = false; + mHomeLongPressed = false; + if (!homeWasLongPressed) { try { - ITelephony telephonyService = getTelephonyService(); - if (telephonyService != null) { - incomingRinging = telephonyService.isRinging(); - } - } catch (RemoteException ex) { - Log.w(TAG, "RemoteException from getPhoneInterface()", ex); + mStatusBarService.cancelPreloadRecentApps(); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException when showing recent apps", e); } - if (incomingRinging) { - Log.i(TAG, "Ignoring HOME; there's a ringing incoming call."); + mHomePressed = false; + if (!canceled) { + // If an incoming call is ringing, HOME is totally disabled. + // (The user is already on the InCallScreen at this point, + // and his ONLY options are to answer or reject the call.) + boolean incomingRinging = false; + try { + ITelephony telephonyService = getTelephonyService(); + if (telephonyService != null) { + incomingRinging = telephonyService.isRinging(); + } + } catch (RemoteException ex) { + Log.w(TAG, "RemoteException from getPhoneInterface()", ex); + } + + if (incomingRinging) { + Log.i(TAG, "Ignoring HOME; there's a ringing incoming call."); + } else { + launchHomeFromHotKey(); + } } else { - launchHomeFromHotKey(); + Log.i(TAG, "Ignoring HOME; event canceled."); } - } else { - Log.i(TAG, "Ignoring HOME; event canceled."); + return -1; } - return -1; } // If a system window has focus, then it doesn't make sense @@ -1666,8 +1688,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } } - if (down) { + if (!mHomePressed) { + try { + mStatusBarService.preloadRecentApps(); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException when preloading recent apps", e); + } + } if (repeatCount == 0) { mHomePressed = true; } else if ((event.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) { @@ -1939,10 +1967,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { // When the user taps down, we re-show the nav bar. boolean changed = false; synchronized (mLock) { - // Any user activity always causes us to show the navigation controls, - // if they had been hidden. - int newVal = mResettingSystemUiFlags - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + // Any user activity always causes us to show the + // navigation controls, if they had been hidden. + // We also clear the low profile and only content + // flags so that tapping on the screen will atomically + // restore all currently hidden screen decorations. + int newVal = mResettingSystemUiFlags | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_LOW_PROFILE | + View.SYSTEM_UI_FLAG_FULLSCREEN; if (mResettingSystemUiFlags != newVal) { mResettingSystemUiFlags = newVal; changed = true; @@ -1950,14 +1983,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { // We don't allow the system's nav bar to be hidden // again for 1 second, to prevent applications from // spamming us and keeping it from being shown. - newVal = mForceClearedSystemUiFlags - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + newVal = mForceClearedSystemUiFlags | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; if (mForceClearedSystemUiFlags != newVal) { mForceClearedSystemUiFlags = newVal; changed = true; mHandler.postDelayed(new Runnable() { @Override public void run() { synchronized (mLock) { + // Clear flags. mForceClearedSystemUiFlags &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; } @@ -1998,17 +2032,33 @@ public class PhoneWindowManager implements WindowManagerPolicy { public void getContentInsetHintLw(WindowManager.LayoutParams attrs, Rect contentInset) { final int fl = attrs.flags; - + if ((fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_FULLSCREEN | FLAG_LAYOUT_INSET_DECOR)) == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) { - contentInset.set(mCurLeft, mCurTop, - (mRestrictedScreenLeft+mRestrictedScreenWidth) - mCurRight, - (mRestrictedScreenTop+mRestrictedScreenHeight) - mCurBottom); - } else { - contentInset.setEmpty(); + int availRight, availBottom; + if ((attrs.systemUiVisibility & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) { + availRight = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth; + availBottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight; + } else { + availRight = mRestrictedScreenLeft + mRestrictedScreenWidth; + availBottom = mRestrictedScreenTop + mRestrictedScreenHeight; + } + if ((attrs.systemUiVisibility & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) { + contentInset.set(mStableLeft, mStableTop, + availRight - mStableRight, availBottom - mStableBottom); + } else if ((attrs.systemUiVisibility & (View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)) == 0) { + contentInset.set(mCurLeft, mCurTop, + availRight - mCurRight, availBottom - mCurBottom); + } else { + contentInset.set(mCurLeft, mCurTop, + availRight - mCurRight, availBottom - mCurBottom); + } + return; } + contentInset.setEmpty(); } - + /** {@inheritDoc} */ public void beginLayoutLw(int displayWidth, int displayHeight, int displayRotation) { mUnrestrictedScreenLeft = mUnrestrictedScreenTop = 0; @@ -2017,10 +2067,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { mRestrictedScreenLeft = mRestrictedScreenTop = 0; mRestrictedScreenWidth = displayWidth; mRestrictedScreenHeight = displayHeight; - mDockLeft = mContentLeft = mCurLeft = 0; - mDockTop = mContentTop = mCurTop = 0; - mDockRight = mContentRight = mCurRight = displayWidth; - mDockBottom = mContentBottom = mCurBottom = displayHeight; + mDockLeft = mContentLeft = mStableLeft = mCurLeft = 0; + mDockTop = mContentTop = mStableTop = mCurTop = 0; + mDockRight = mContentRight = mStableRight = mCurRight = displayWidth; + mDockBottom = mContentBottom = mStableBottom = mCurBottom = displayHeight; mDockLayer = 0x10000000; // start with the current dock rect, which will be (0,0,displayWidth,displayHeight) @@ -2062,6 +2112,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Portrait screen; nav bar goes on bottom. mTmpNavigationFrame.set(0, displayHeight-mNavigationBarHeight, displayWidth, displayHeight); + mStableBottom = mTmpNavigationFrame.top; if (navVisible) { mDockBottom = mTmpNavigationFrame.top; mRestrictedScreenHeight = mDockBottom - mDockTop; @@ -2075,6 +2126,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { // Landscape screen; nav bar goes to the right. mTmpNavigationFrame.set(displayWidth-mNavigationBarWidth, 0, displayWidth, displayHeight); + mStableRight = mTmpNavigationFrame.left; if (navVisible) { mDockRight = mTmpNavigationFrame.left; mRestrictedScreenWidth = mDockRight - mDockLeft; @@ -2106,11 +2158,23 @@ public class PhoneWindowManager implements WindowManagerPolicy { pf.bottom = df.bottom = vf.bottom = mDockBottom; mStatusBar.computeFrameLw(pf, df, vf, vf); + final Rect r = mStatusBar.getFrameLw(); + + // Compute the stable dimensions whether or not the status bar is hidden. + if (mStatusBarCanHide) { + if (mDockTop == r.top) mStableTop = r.bottom; + else if (mDockBottom == r.bottom) mStableBottom = r.top; + } else { + if (mStableTop == r.top) { + mStableTop = r.bottom; + } else if (mStableBottom == r.bottom) { + mStableBottom = r.top; + } + } if (mStatusBar.isVisibleLw()) { // If the status bar is hidden, we don't want to cause // windows behind it to scroll. - final Rect r = mStatusBar.getFrameLw(); if (mStatusBarCanHide) { // Status bar may go away, so the screen area it occupies // is available to apps but just covering them when the @@ -2210,7 +2274,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { final int fl = attrs.flags; final int sim = attrs.softInputMode; - + final int sysUiFl = win.getSystemUiVisibility(); + final Rect pf = mTmpParentFrame; final Rect df = mTmpDisplayFrame; final Rect cf = mTmpContentFrame; @@ -2231,7 +2296,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { final int adjust = sim & SOFT_INPUT_MASK_ADJUST; if ((fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_FULLSCREEN | FLAG_LAYOUT_INSET_DECOR)) - == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) { + == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR) + && (sysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { if (DEBUG_LAYOUT) Log.v(TAG, "layoutWindowLw(" + attrs.getTitle() + "): IN_SCREEN, INSET_DECOR, !FULLSCREEN"); @@ -2267,6 +2333,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { "Laying out status bar window: (%d,%d - %d,%d)", pf.left, pf.top, pf.right, pf.bottom)); } + } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0 + && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW + && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { + // Asking for layout as if the nav bar is hidden, lets the + // application extend into the unrestricted screen area. We + // only do this for application windows to ensure no window that + // can be above the nav bar can do this. + pf.left = df.left = mUnrestrictedScreenLeft; + pf.top = df.top = mUnrestrictedScreenTop; + pf.right = df.right = mUnrestrictedScreenLeft+mUnrestrictedScreenWidth; + pf.bottom = df.bottom = mUnrestrictedScreenTop+mUnrestrictedScreenHeight; } else { pf.left = df.left = mRestrictedScreenLeft; pf.top = df.top = mRestrictedScreenTop; @@ -2284,6 +2361,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { cf.right = mContentRight; cf.bottom = mContentBottom; } + if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) { + // If app is requesting a stable layout, don't let the + // content insets go below the stable values. + if (cf.left < mStableLeft) cf.left = mStableLeft; + if (cf.top < mStableTop) cf.top = mStableTop; + if (cf.right > mStableRight) cf.right = mStableRight; + if (cf.bottom > mStableBottom) cf.bottom = mStableBottom; + } if (adjust != SOFT_INPUT_ADJUST_NOTHING) { vf.left = mCurLeft; vf.top = mCurTop; @@ -2293,7 +2378,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { vf.set(cf); } } - } else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0) { + } else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0 || (sysUiFl + & (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) { if (DEBUG_LAYOUT) Log.v(TAG, "layoutWindowLw(" + attrs.getTitle() + "): IN_SCREEN"); // A window that has requested to fill the entire screen just @@ -2308,7 +2395,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { pf.bottom = df.bottom = cf.bottom = hasNavBar ? mRestrictedScreenTop+mRestrictedScreenHeight : mUnrestrictedScreenTop+mUnrestrictedScreenHeight; - if (DEBUG_LAYOUT) { Log.v(TAG, String.format( "Laying out IN_SCREEN status bar window: (%d,%d - %d,%d)", @@ -2340,6 +2426,21 @@ public class PhoneWindowManager implements WindowManagerPolicy { pf.right = df.right = cf.right = mUnrestrictedScreenLeft+mUnrestrictedScreenWidth; pf.bottom = df.bottom = cf.bottom = mUnrestrictedScreenTop+mUnrestrictedScreenHeight; + } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0 + && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW + && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { + // Asking for layout as if the nav bar is hidden, lets the + // application extend into the unrestricted screen area. We + // only do this for application windows to ensure no window that + // can be above the nav bar can do this. + // XXX This assumes that an app asking for this will also + // ask for layout in only content. We can't currently figure out + // what the screen would be if only laying out to hide the nav bar. + pf.left = df.left = cf.left = mUnrestrictedScreenLeft; + pf.top = df.top = cf.top = mUnrestrictedScreenTop; + pf.right = df.right = cf.right = mUnrestrictedScreenLeft+mUnrestrictedScreenWidth; + pf.bottom = df.bottom = cf.bottom + = mUnrestrictedScreenTop+mUnrestrictedScreenHeight; } else { pf.left = df.left = cf.left = mRestrictedScreenLeft; pf.top = df.top = cf.top = mRestrictedScreenTop; @@ -2347,6 +2448,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { pf.bottom = df.bottom = cf.bottom = mRestrictedScreenTop+mRestrictedScreenHeight; } + if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) { + // If app is requesting a stable layout, don't let the + // content insets go below the stable values. + if (cf.left < mStableLeft) cf.left = mStableLeft; + if (cf.top < mStableTop) cf.top = mStableTop; + if (cf.right > mStableRight) cf.right = mStableRight; + if (cf.bottom > mStableBottom) cf.bottom = mStableBottom; + } if (adjust != SOFT_INPUT_ADJUST_NOTHING) { vf.left = mCurLeft; vf.top = mCurTop; @@ -2507,7 +2616,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { Log.d(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs() + " lp.flags=0x" + Integer.toHexString(lp.flags)); } - topIsFullscreen = (lp.flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0; + topIsFullscreen = (lp.flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0 + || (mLastSystemUiFlags & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0; // The subtle difference between the window for mTopFullscreenOpaqueWindowState // and mTopIsFullscreen is that that mTopIsFullscreen is set only if the window // has the FLAG_FULLSCREEN set. Not sure if there is another way that to be the @@ -2572,7 +2682,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - if ((updateSystemUiVisibilityLw()&View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0) { + if ((updateSystemUiVisibilityLw()&SYSTEM_UI_CHANGING_LAYOUT) != 0) { // If the navigation bar has been hidden or shown, we need to do another // layout pass to update that window. changes |= FINISH_LAYOUT_REDO_LAYOUT; @@ -2619,7 +2729,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { public int focusChangedLw(WindowState lastFocus, WindowState newFocus) { mFocusedWindow = newFocus; - if ((updateSystemUiVisibilityLw()&View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0) { + if ((updateSystemUiVisibilityLw()&SYSTEM_UI_CHANGING_LAYOUT) != 0) { // If the navigation bar has been hidden or shown, we need to do another // layout pass to update that window. return FINISH_LAYOUT_REDO_LAYOUT; @@ -3979,7 +4089,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } if (mStatusBarService != null) { try { - mStatusBarService.setSystemUiVisibility(visibility); + mStatusBarService.setSystemUiVisibility(visibility, 0xffffffff); mStatusBarService.topAppWindowChanged(needsMenu); } catch (RemoteException e) { // not much to be done @@ -4046,6 +4156,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { pw.print(","); pw.print(mRestrictedScreenTop); pw.print(") "); pw.print(mRestrictedScreenWidth); pw.print("x"); pw.println(mRestrictedScreenHeight); + pw.print(prefix); pw.print("mStable=("); pw.print(mStableLeft); + pw.print(","); pw.print(mStableTop); + pw.print(")-("); pw.print(mStableRight); + pw.print(","); pw.print(mStableBottom); pw.println(")"); pw.print(prefix); pw.print("mCur=("); pw.print(mCurLeft); pw.print(","); pw.print(mCurTop); pw.print(")-("); pw.print(mCurRight); diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index c70564658576..96ee2f037de8 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -1132,6 +1132,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private boolean needsToShowImeSwitchOngoingNotification() { if (!mShowOngoingImeSwitcherForPhones) return false; + if (isScreenLocked()) return false; synchronized (mMethodMap) { List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked(); final int N = imis.size(); @@ -2148,15 +2149,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mContext.startActivity(intent); } + private boolean isScreenLocked() { + return mKeyguardManager != null + && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure(); + } private void showInputMethodMenuInternal(boolean showSubtypes) { if (DEBUG) Slog.v(TAG, "Show switching menu"); final Context context = mContext; final PackageManager pm = context.getPackageManager(); - final boolean isScreenLocked = mKeyguardManager != null - && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure(); + final boolean isScreenLocked = isScreenLocked(); - String lastInputMethodId = Settings.Secure.getString(context + final String lastInputMethodId = Settings.Secure.getString(context .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId); if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId); diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java index a9ff6c589df9..84290869804c 100644 --- a/services/java/com/android/server/StatusBarManagerService.java +++ b/services/java/com/android/server/StatusBarManagerService.java @@ -292,27 +292,27 @@ public class StatusBarManagerService extends IStatusBarService.Stub } } - public void setSystemUiVisibility(int vis) { + public void setSystemUiVisibility(int vis, int mask) { // also allows calls from window manager which is in this process. enforceStatusBarService(); if (SPEW) Slog.d(TAG, "setSystemUiVisibility(0x" + Integer.toHexString(vis) + ")"); synchronized (mLock) { - updateUiVisibilityLocked(vis); + updateUiVisibilityLocked(vis, mask); disableLocked(vis & StatusBarManager.DISABLE_MASK, mSysUiVisToken, "WindowManager.LayoutParams"); } } - private void updateUiVisibilityLocked(final int vis) { + private void updateUiVisibilityLocked(final int vis, final int mask) { if (mSystemUiVisibility != vis) { mSystemUiVisibility = vis; mHandler.post(new Runnable() { public void run() { if (mBar != null) { try { - mBar.setSystemUiVisibility(vis); + mBar.setSystemUiVisibility(vis, mask); } catch (RemoteException ex) { } } @@ -352,6 +352,24 @@ public class StatusBarManagerService extends IStatusBarService.Stub } } + @Override + public void preloadRecentApps() { + if (mBar != null) { + try { + mBar.preloadRecentApps(); + } catch (RemoteException ex) {} + } + } + + @Override + public void cancelPreloadRecentApps() { + if (mBar != null) { + try { + mBar.cancelPreloadRecentApps(); + } catch (RemoteException ex) {} + } + } + private void enforceStatusBar() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR, "StatusBarManagerService"); diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index bbaebd9b3fc6..ab4012f29f42 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -3587,11 +3587,9 @@ public final class ActivityManagerService extends ActivityManagerNative // If uid is specified and the uid and process name match // Or, the uid is not specified and the process name matches } else if (((uid > 0 && uid != Process.SYSTEM_UID && app.info.uid == uid) - && (app.processName.equals(packageName) - || app.processName.startsWith(procNamePrefix))) - || (uid < 0 - && (app.processName.equals(packageName) - || app.processName.startsWith(procNamePrefix)))) { + || ((app.processName.equals(packageName) + || app.processName.startsWith(procNamePrefix)) + && uid < 0))) { if (app.setAdj >= minOomAdj) { if (!doit) { return true; diff --git a/services/java/com/android/server/wm/AppWindowToken.java b/services/java/com/android/server/wm/AppWindowToken.java index c24c2d91d785..3069b74de9bd 100644 --- a/services/java/com/android/server/wm/AppWindowToken.java +++ b/services/java/com/android/server/wm/AppWindowToken.java @@ -376,7 +376,8 @@ class AppWindowToken extends WindowToken { int numDrawn = 0; boolean nowGone = true; - if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "Update reported visibility: " + this); + if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, + "Update reported visibility: " + this); final int N = allAppWindows.size(); for (int i=0; i<N; i++) { WindowState win = allAppWindows.get(i); @@ -393,8 +394,7 @@ class AppWindowToken extends WindowToken { if (!win.isDrawnLw()) { Slog.v(WindowManagerService.TAG, "Not displayed: s=" + win.mWinAnimator.mSurface + " pv=" + win.mPolicyVisibility - + " dp=" + win.mDrawPending - + " cdp=" + win.mCommitDrawPending + + " mDrawState=" + win.mWinAnimator.mDrawState + " ah=" + win.mAttachedHidden + " th=" + (win.mAppToken != null diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java index eddfe9217d9a..5a104b228092 100644 --- a/services/java/com/android/server/wm/WindowAnimator.java +++ b/services/java/com/android/server/wm/WindowAnimator.java @@ -4,6 +4,9 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; +import static com.android.server.wm.WindowManagerService.LayoutFields.SET_UPDATE_ROTATION; +import static com.android.server.wm.WindowManagerService.LayoutFields.SET_WALLPAPER_MAY_CHANGE; + import android.content.Context; import android.os.SystemClock; import android.util.Log; @@ -30,7 +33,6 @@ public class WindowAnimator { final WindowManagerPolicy mPolicy; boolean mAnimating; - boolean mUpdateRotation; boolean mTokenMayBeDrawn; boolean mForceHiding; WindowState mWindowAnimationBackground; @@ -61,9 +63,10 @@ public class WindowAnimator { // seen. WindowState mWindowDetachedWallpaper = null; WindowState mDetachedWallpaper = null; - boolean mWallpaperMayChange; DimSurface mWindowAnimationBackgroundSurface = null; + int mBulkUpdateParams = 0; + WindowAnimator(final WindowManagerService service, final Context context, final WindowManagerPolicy policy) { mService = service; @@ -77,7 +80,7 @@ public class WindowAnimator { "Detached wallpaper changed from " + mWindowDetachedWallpaper + " to " + mDetachedWallpaper); mWindowDetachedWallpaper = mDetachedWallpaper; - mWallpaperMayChange = true; + mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE; } if (mWindowAnimationBackgroundColor != 0) { @@ -147,10 +150,9 @@ public class WindowAnimator { (mScreenRotationAnimation.isAnimating() || mScreenRotationAnimation.mFinishAnimReady)) { if (mScreenRotationAnimation.stepAnimationLocked(mCurrentTime)) { - mUpdateRotation = false; mAnimating = true; } else { - mUpdateRotation = true; + mBulkUpdateParams |= SET_UPDATE_ROTATION; mScreenRotationAnimation.kill(); mScreenRotationAnimation = null; } @@ -217,7 +219,7 @@ public class WindowAnimator { } if (wasAnimating && !winAnimator.mAnimating && mService.mWallpaperTarget == w) { - mWallpaperMayChange = true; + mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE; mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 2"); @@ -270,7 +272,7 @@ public class WindowAnimator { } if (changed && (attrs.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { - mWallpaperMayChange = true; + mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE; mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 4"); @@ -297,8 +299,7 @@ public class WindowAnimator { if (!w.isDrawnLw()) { Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurface + " pv=" + w.mPolicyVisibility - + " dp=" + w.mDrawPending - + " cdp=" + w.mCommitDrawPending + + " mDrawState=" + winAnimator.mDrawState + " ah=" + w.mAttachedHidden + " th=" + atoken.hiddenRequested + " a=" + winAnimator.mAnimating); @@ -321,7 +322,7 @@ public class WindowAnimator { atoken.startingDisplayed = true; } } - } else if (w.mReadyToShow) { + } else if (winAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW) { if (winAnimator.performShowLocked()) { mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { @@ -386,7 +387,6 @@ public class WindowAnimator { private void performAnimationsLocked() { mTokenMayBeDrawn = false; - mService.mInnerFields.mWallpaperMayChange = false; mForceHiding = false; mDetachedWallpaper = null; mWindowAnimationBackground = null; @@ -399,11 +399,10 @@ public class WindowAnimator { } } - void animate() { mPendingLayoutChanges = 0; - mWallpaperMayChange = false; mCurrentTime = SystemClock.uptimeMillis(); + mBulkUpdateParams = 0; // Update animations of all applications, including those // associated with exiting/removed apps @@ -445,8 +444,8 @@ public class WindowAnimator { Surface.closeTransaction(); } - if (mWallpaperMayChange) { - mService.notifyWallpaperMayChange(); + if (mBulkUpdateParams != 0) { + mService.bulkSetParameters(mBulkUpdateParams); } } diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index cecc0a0df9c5..afbc3489d43e 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -149,7 +149,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs { static final String TAG = "WindowManager"; static final boolean DEBUG = false; - static final boolean DEBUG_ADD_REMOVE = false; + static final boolean DEBUG_ADD_REMOVE = true; static final boolean DEBUG_FOCUS = false; static final boolean DEBUG_ANIM = false; static final boolean DEBUG_LAYOUT = false; @@ -158,7 +158,7 @@ public class WindowManagerService extends IWindowManager.Stub static final boolean DEBUG_INPUT = false; static final boolean DEBUG_INPUT_METHOD = false; static final boolean DEBUG_VISIBILITY = false; - static final boolean DEBUG_WINDOW_MOVEMENT = false; + static final boolean DEBUG_WINDOW_MOVEMENT = true; static final boolean DEBUG_TOKEN_MOVEMENT = false; static final boolean DEBUG_ORIENTATION = false; static final boolean DEBUG_APP_ORIENTATION = false; @@ -589,7 +589,10 @@ public class WindowManagerService extends IWindowManager.Stub /** Pulled out of performLayoutAndPlaceSurfacesLockedInner in order to refactor into multiple * methods. */ - class LayoutAndSurfaceFields { + class LayoutFields { + static final int SET_UPDATE_ROTATION = 1 << 0; + static final int SET_WALLPAPER_MAY_CHANGE = 1 << 1; + boolean mWallpaperForceHidingChanged = false; boolean mWallpaperMayChange = false; boolean mOrientationChangeComplete = true; @@ -602,7 +605,7 @@ public class WindowManagerService extends IWindowManager.Stub private float mButtonBrightness = -1; private boolean mUpdateRotation = false; } - LayoutAndSurfaceFields mInnerFields = new LayoutAndSurfaceFields(); + LayoutFields mInnerFields = new LayoutFields(); /** Only do a maximum of 6 repeated layouts. After that quit */ private int mLayoutRepeatCount; @@ -1548,6 +1551,7 @@ public class WindowManagerService extends IWindowManager.Stub static final int ADJUST_WALLPAPER_VISIBILITY_CHANGED = 1<<2; int adjustWallpaperWindowsLocked() { + mInnerFields.mWallpaperMayChange = false; int changed = 0; final int dw = mAppDisplayWidth; @@ -1585,8 +1589,7 @@ public class WindowManagerService extends IWindowManager.Stub } } if (DEBUG_WALLPAPER) Slog.v(TAG, "Win " + w + ": readyfordisplay=" - + w.isReadyForDisplay() + " drawpending=" + w.mDrawPending - + " commitdrawpending=" + w.mCommitDrawPending); + + w.isReadyForDisplay() + " mDrawState=" + w.mWinAnimator.mDrawState); if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0 && w.isReadyForDisplay() && (mWallpaperTarget == w || w.isDrawnLw())) { if (DEBUG_WALLPAPER) Slog.v(TAG, @@ -1968,6 +1971,11 @@ public class WindowManagerService extends IWindowManager.Stub } } + // TODO(cmautner): Move to WindowAnimator. + void setWallpaperOffset(final WindowStateAnimator winAnimator, final int left, final int top) { + mH.sendMessage(mH.obtainMessage(H.SET_WALLPAPER_OFFSET, left, top, winAnimator)); + } + void updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) { final int dw = mAppDisplayWidth; final int dh = mAppDisplayHeight; @@ -2006,10 +2014,8 @@ public class WindowManagerService extends IWindowManager.Stub if (SHOW_TRANSACTIONS) logSurface(wallpaper, "POS " + wallpaper.mShownFrame.left + ", " + wallpaper.mShownFrame.top, null); - winAnimator.mSurfaceX = wallpaper.mShownFrame.left; - winAnimator.mSurfaceY = wallpaper.mShownFrame.top; - winAnimator.mSurface.setPosition(wallpaper.mShownFrame.left, - wallpaper.mShownFrame.top); + setWallpaperOffset(winAnimator, (int) wallpaper.mShownFrame.left, + (int) wallpaper.mShownFrame.top); } catch (RuntimeException e) { Slog.w(TAG, "Error positioning surface of " + wallpaper + " pos=(" + wallpaper.mShownFrame.left @@ -2310,7 +2316,7 @@ public class WindowManagerService extends IWindowManager.Stub // to hold off on removing the window until the animation is done. // If the display is frozen, just remove immediately, since the // animation wouldn't be seen. - if (win.mWinAnimator.mSurface != null && okToDisplay()) { + if (win.mHasSurface && okToDisplay()) { // If we are not currently running the exit animation, we // need to see about starting one. wasVisible = win.isWinVisibleLw(); @@ -2454,8 +2460,7 @@ public class WindowManagerService extends IWindowManager.Stub } static void logSurface(WindowState w, String msg, RuntimeException where) { - String str = " SURFACE " + Integer.toHexString(w.hashCode()) - + ": " + msg + " / " + w.mAttrs.getTitle(); + String str = " SURFACE " + msg + ": " + w; if (where != null) { Slog.i(TAG, str, where); } else { @@ -2471,26 +2476,20 @@ public class WindowManagerService extends IWindowManager.Stub Slog.i(TAG, str); } } - + + // TODO(cmautner): Move to WindowStateAnimator. + void setTransparentRegionHint(final WindowStateAnimator winAnimator, final Region region) { + mH.sendMessage(mH.obtainMessage(H.SET_TRANSPARENT_REGION, + new Pair<WindowStateAnimator, Region>(winAnimator, region))); + } + void setTransparentRegionWindow(Session session, IWindow client, Region region) { long origId = Binder.clearCallingIdentity(); try { synchronized (mWindowMap) { WindowState w = windowForClientLocked(session, client, false); - WindowStateAnimator winAnimator = w.mWinAnimator; - if ((w != null) && (winAnimator.mSurface != null)) { - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, - ">>> OPEN TRANSACTION setTransparentRegion"); - Surface.openTransaction(); - try { - if (SHOW_TRANSACTIONS) logSurface(w, - "transparentRegionHint=" + region, null); - winAnimator.mSurface.setTransparentRegionHint(region); - } finally { - Surface.closeTransaction(); - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, - "<<< CLOSE TRANSACTION setTransparentRegion"); - } + if ((w != null) && w.mHasSurface) { + setTransparentRegionHint(w.mWinAnimator, region); } } } finally { @@ -2613,11 +2612,12 @@ public class WindowManagerService extends IWindowManager.Stub long origId = Binder.clearCallingIdentity(); synchronized(mWindowMap) { + // TODO(cmautner): synchronize on mAnimator or win.mWinAnimator. WindowState win = windowForClientLocked(session, client, false); - WindowStateAnimator winAnimator = win.mWinAnimator; if (win == null) { return 0; } + WindowStateAnimator winAnimator = win.mWinAnimator; if (win.mRequestedWidth != requestedWidth || win.mRequestedHeight != requestedHeight) { win.mLayoutNeeded = true; @@ -2644,7 +2644,8 @@ public class WindowManagerService extends IWindowManager.Stub } flagChanges = win.mAttrs.flags ^= attrs.flags; attrChanges = win.mAttrs.copyFrom(attrs); - if ((attrChanges&WindowManager.LayoutParams.LAYOUT_CHANGED) != 0) { + if ((attrChanges & (WindowManager.LayoutParams.LAYOUT_CHANGED + | WindowManager.LayoutParams.SYSTEM_UI_VISIBILITY_CHANGED)) != 0) { win.mLayoutNeeded = true; } } @@ -2697,6 +2698,7 @@ public class WindowManagerService extends IWindowManager.Stub displayed = !win.isVisibleLw(); if (win.mExiting) { winAnimator.cancelExitAnimationForNextAnimationLocked(); + win.mExiting = false; } if (win.mDestroying) { win.mDestroying = false; @@ -2735,7 +2737,7 @@ public class WindowManagerService extends IWindowManager.Stub surfaceChanged = true; } try { - if (winAnimator.mSurface == null) { + if (!win.mHasSurface) { surfaceChanged = true; } Surface surface = winAnimator.createSurfaceLocked(); @@ -2945,7 +2947,7 @@ public class WindowManagerService extends IWindowManager.Stub final long origId = Binder.clearCallingIdentity(); synchronized(mWindowMap) { WindowState win = windowForClientLocked(session, client, false); - if (win != null && win.finishDrawingLocked()) { + if (win != null && win.mWinAnimator.finishDrawingLocked()) { if ((win.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0) { adjustWallpaperWindowsLocked(); } @@ -3411,15 +3413,13 @@ public class WindowManagerService extends IWindowManager.Stub } public int getOrientationFromAppTokensLocked() { - int pos = mAppTokens.size() - 1; int curGroup = 0; int lastOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; boolean findingBehind = false; boolean haveGroup = false; boolean lastFullscreen = false; - while (pos >= 0) { + for (int pos = mAppTokens.size() - 1; pos >= 0; pos--) { AppWindowToken wtoken = mAppTokens.get(pos); - pos--; if (DEBUG_APP_ORIENTATION) Slog.v(TAG, "Checking app orientation: " + wtoken); @@ -4133,7 +4133,7 @@ public class WindowManagerService extends IWindowManager.Stub WindowState w = wtoken.allAppWindows.get(i); if (w.mAppFreezing) { w.mAppFreezing = false; - if (w.mWinAnimator.mSurface != null && !w.mOrientationChanging) { + if (w.mHasSurface && !w.mOrientationChanging) { if (DEBUG_ORIENTATION) Slog.v(TAG, "set mOrientationChanging of " + w); w.mOrientationChanging = true; } @@ -4721,7 +4721,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { for (int i=mWindows.size()-1; i>=0; i--) { WindowState w = mWindows.get(i); - if (w.mWinAnimator.mSurface != null) { + if (w.mHasSurface) { try { w.mClient.closeSystemDialogs(reason); } catch (RemoteException e) { @@ -5193,7 +5193,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean including = false; for (int i=mWindows.size()-1; i>=0; i--) { WindowState ws = mWindows.get(i); - if (ws.mWinAnimator.mSurface == null) { + if (!ws.mHasSurface) { continue; } if (ws.mLayer >= aboveAppLayer) { @@ -5507,7 +5507,7 @@ public class WindowManagerService extends IWindowManager.Stub for (int i=mWindows.size()-1; i>=0; i--) { WindowState w = mWindows.get(i); - if (w.mWinAnimator.mSurface != null) { + if (w.mHasSurface) { if (DEBUG_ORIENTATION) Slog.v(TAG, "Set mOrientationChanging of " + w); w.mOrientationChanging = true; } @@ -6652,6 +6652,11 @@ public class WindowManagerService extends IWindowManager.Stub public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22; public static final int BOOT_TIMEOUT = 23; public static final int WAITING_FOR_DRAWN_TIMEOUT = 24; + public static final int BULK_UPDATE_PARAMETERS = 25; + + public static final int ANIMATOR_WHAT_OFFSET = 100000; + public static final int SET_TRANSPARENT_REGION = ANIMATOR_WHAT_OFFSET + 1; + public static final int SET_WALLPAPER_OFFSET = ANIMATOR_WHAT_OFFSET + 2; private Session mLastReportedHold; @@ -7062,6 +7067,43 @@ public class WindowManagerService extends IWindowManager.Stub } break; } + + case BULK_UPDATE_PARAMETERS: { + synchronized (mWindowMap) { + // TODO(cmautner): As the number of bits grows, use masks of bit groups to + // eliminate unnecessary tests. + if ((msg.arg1 & LayoutFields.SET_UPDATE_ROTATION) != 0) { + mInnerFields.mUpdateRotation = true; + } + if ((msg.arg1 & LayoutFields.SET_WALLPAPER_MAY_CHANGE) != 0) { + mInnerFields.mWallpaperMayChange = true; + } + + requestTraversalLocked(); + } + break; + } + + // Animation messages. Move to Window{State}Animator + case SET_TRANSPARENT_REGION: { + // TODO(cmautner): Remove sync. + synchronized (mWindowMap) { + Pair<WindowStateAnimator, Region> pair = + (Pair<WindowStateAnimator, Region>) msg.obj; + final WindowStateAnimator winAnimator = pair.first; + winAnimator.setTransparentRegionHint(pair.second); + } + break; + } + + case SET_WALLPAPER_OFFSET: { + // TODO(cmautner): Remove sync. + synchronized (mWindowMap) { + final WindowStateAnimator winAnimator = (WindowStateAnimator) msg.obj; + winAnimator.setWallpaperOffset(msg.arg1, msg.arg2); + } + break; + } } } } @@ -7996,7 +8038,6 @@ public class WindowManagerService extends IWindowManager.Stub } } mInnerFields.mAdjResult |= adjustWallpaperWindowsLocked(); - mInnerFields.mWallpaperMayChange = false; mInnerFields.mWallpaperForceHidingChanged = false; if (DEBUG_WALLPAPER) Slog.v(TAG, "****** OLD: " + oldWallpaper + " NEW: " + mWallpaperTarget @@ -8007,7 +8048,7 @@ public class WindowManagerService extends IWindowManager.Stub mAnimator.mForceHiding = false; for (int i=mWindows.size()-1; i>=0; i--) { WindowState w = mWindows.get(i); - if (w.mWinAnimator.mSurface != null) { + if (w.mHasSurface) { final WindowManager.LayoutParams attrs = w.mAttrs; if (mPolicy.doesForceHide(w, attrs) && w.isVisibleLw()) { if (DEBUG_FOCUS) Slog.i(TAG, "win=" + w + " force hides other windows"); @@ -8027,6 +8068,7 @@ public class WindowManagerService extends IWindowManager.Stub } private void updateResizingWindows(final WindowState w) { + final WindowStateAnimator winAnimator = w.mWinAnimator; if (!w.mAppFreezing && w.mLayoutSeq == mLayoutSeq) { w.mContentInsetsChanged |= !w.mLastContentInsets.equals(w.mContentInsets); @@ -8046,7 +8088,7 @@ public class WindowManagerService extends IWindowManager.Stub w.mLastFrame.set(w.mFrame); if (w.mContentInsetsChanged || w.mVisibleInsetsChanged - || w.mWinAnimator.mSurfaceResized + || winAnimator.mSurfaceResized || configChanged) { if (DEBUG_RESIZE || DEBUG_ORIENTATION) { Slog.v(TAG, "Resize reasons: " @@ -8068,9 +8110,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation start waiting for draw in " + w + ", surface " + w.mWinAnimator.mSurface); - w.mDrawPending = true; - w.mCommitDrawPending = false; - w.mReadyToShow = false; + winAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING; if (w.mAppToken != null) { w.mAppToken.allDrawn = false; } @@ -8106,7 +8146,7 @@ public class WindowManagerService extends IWindowManager.Stub final int attrFlags = attrs.flags; final boolean canBeSeen = w.isDisplayedLw(); - if (w.mWinAnimator.mSurface != null) { + if (w.mHasSurface) { if ((attrFlags&FLAG_KEEP_SCREEN_ON) != 0) { mInnerFields.mHoldScreen = w.mSession; } @@ -8258,7 +8298,7 @@ public class WindowManagerService extends IWindowManager.Stub mPolicy.beginAnimationLw(dw, dh); for (i = mWindows.size() - 1; i >= 0; i--) { WindowState w = mWindows.get(i); - if (w.mWinAnimator.mSurface != null) { + if (w.mHasSurface) { mPolicy.animatingWindowLw(w, w.mAttrs); } } @@ -8376,9 +8416,9 @@ public class WindowManagerService extends IWindowManager.Stub updateResizingWindows(w); // Moved from updateWindowsAndWallpaperLocked(). - if (winAnimator.mSurface != null) { + if (w.mHasSurface) { // Take care of the window being ready to display. - if (w.commitFinishDrawingLocked(currentTime)) { + if (winAnimator.commitFinishDrawingLocked(currentTime)) { if ((w.mAttrs.flags & WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) { if (WindowManagerService.DEBUG_WALLPAPER) Slog.v(TAG, @@ -8435,11 +8475,10 @@ public class WindowManagerService extends IWindowManager.Stub stopFreezingDisplayLocked(); } - i = mResizingWindows.size(); - if (i > 0) { - do { - i--; + if (!mResizingWindows.isEmpty()) { + for (i = mResizingWindows.size() - 1; i >= 0; i--) { WindowState win = mResizingWindows.get(i); + final WindowStateAnimator winAnimator = win.mWinAnimator; try { if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG, "Reporting new frame to " + win + ": " + win.mCompatFrame); @@ -8451,24 +8490,26 @@ public class WindowManagerService extends IWindowManager.Stub if ((DEBUG_RESIZE || DEBUG_ORIENTATION || DEBUG_CONFIGURATION) && configChanged) { Slog.i(TAG, "Sending new config to window " + win + ": " - + win.mWinAnimator.mSurfaceW + "x" + win.mWinAnimator.mSurfaceH + + winAnimator.mSurfaceW + "x" + winAnimator.mSurfaceH + " / " + mCurConfiguration + " / 0x" + Integer.toHexString(diff)); } win.mConfiguration = mCurConfiguration; - if (DEBUG_ORIENTATION && win.mDrawPending) Slog.i( + if (DEBUG_ORIENTATION && + winAnimator.mDrawState == WindowStateAnimator.DRAW_PENDING) Slog.i( TAG, "Resizing " + win + " WITH DRAW PENDING"); - win.mClient.resized((int)win.mWinAnimator.mSurfaceW, - (int)win.mWinAnimator.mSurfaceH, - win.mLastContentInsets, win.mLastVisibleInsets, win.mDrawPending, + win.mClient.resized((int)winAnimator.mSurfaceW, + (int)winAnimator.mSurfaceH, + win.mLastContentInsets, win.mLastVisibleInsets, + winAnimator.mDrawState == WindowStateAnimator.DRAW_PENDING, configChanged ? win.mConfiguration : null); win.mContentInsetsChanged = false; win.mVisibleInsetsChanged = false; - win.mWinAnimator.mSurfaceResized = false; + winAnimator.mSurfaceResized = false; } catch (RemoteException e) { win.mOrientationChanging = false; } - } while (i > 0); + } mResizingWindows.clear(); } @@ -8703,6 +8744,7 @@ public class WindowManagerService extends IWindowManager.Stub wsa.mSurface.destroy(); wsa.mSurfaceShown = false; wsa.mSurface = null; + ws.mHasSurface = false; mForceRemoves.add(ws); i--; N--; @@ -8715,6 +8757,7 @@ public class WindowManagerService extends IWindowManager.Stub wsa.mSurface.destroy(); wsa.mSurfaceShown = false; wsa.mSurface = null; + ws.mHasSurface = false; leakedSurface = true; } } @@ -8753,6 +8796,7 @@ public class WindowManagerService extends IWindowManager.Stub surface.destroy(); winAnimator.mSurfaceShown = false; winAnimator.mSurface = null; + winAnimator.mWin.mHasSurface = false; } try { @@ -8924,11 +8968,11 @@ public class WindowManagerService extends IWindowManager.Stub mAnimator.mScreenRotationAnimation.kill(); mAnimator.mScreenRotationAnimation = null; } - if (mAnimator.mScreenRotationAnimation == null) { - mAnimator.mScreenRotationAnimation = new ScreenRotationAnimation(mContext, - mFxSession, inTransaction, mCurDisplayWidth, mCurDisplayHeight, - mDisplay.getRotation()); - } + + mAnimator.mScreenRotationAnimation = new ScreenRotationAnimation(mContext, + mFxSession, inTransaction, mCurDisplayWidth, mCurDisplayHeight, + mDisplay.getRotation()); + if (!mAnimator.mScreenRotationAnimation.hasScreenshot()) { Surface.freezeDisplay(0); } @@ -8943,6 +8987,10 @@ public class WindowManagerService extends IWindowManager.Stub } if (mWaitingForConfig || mAppsFreezingScreen > 0 || mWindowsFreezingScreen) { + if (DEBUG_ORIENTATION) Slog.d(TAG, + "stopFreezingDisplayLocked: Returning mWaitingForConfig=" + mWaitingForConfig + + ", mAppsFreezingScreen=" + mAppsFreezingScreen + + ", mWindowsFreezingScreen=" + mWindowsFreezingScreen); return; } @@ -9643,15 +9691,14 @@ public class WindowManagerService extends IWindowManager.Stub requestTraversalLocked(); } - void notifyWallpaperMayChange() { - mInnerFields.mWallpaperMayChange = true; - requestTraversalLocked(); - } - void debugLayoutRepeats(final String msg) { if (mLayoutRepeatCount >= LAYOUT_REPEAT_THRESHOLD) { Slog.v(TAG, "Layouts looping: " + msg); Slog.v(TAG, "mPendingLayoutChanges = 0x" + Integer.toHexString(mPendingLayoutChanges)); } } + + void bulkSetParameters(final int bulkUpdateParams) { + mH.sendMessage(mH.obtainMessage(H.BULK_UPDATE_PARAMETERS, bulkUpdateParams, 0)); + } } diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java index 789bfaaddc9c..b74aa6143029 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -19,13 +19,10 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; -import com.android.server.wm.WindowManagerService.H; - import android.content.Context; import android.content.res.Configuration; import android.graphics.Matrix; @@ -40,15 +37,10 @@ import android.view.Gravity; import android.view.IApplicationToken; import android.view.IWindow; import android.view.InputChannel; -import android.view.Surface; import android.view.View; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.WindowManagerPolicy; -import android.view.WindowManager.LayoutParams; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.view.animation.Transformation; import java.io.PrintWriter; import java.util.ArrayList; @@ -57,6 +49,8 @@ import java.util.ArrayList; * A window in the window manager. */ final class WindowState implements WindowManagerPolicy.WindowState { + static final String TAG = "WindowState"; + static final boolean DEBUG_VISIBILITY = WindowManagerService.DEBUG_VISIBILITY; static final boolean SHOW_TRANSACTIONS = WindowManagerService.SHOW_TRANSACTIONS; static final boolean SHOW_LIGHT_TRANSACTIONS = WindowManagerService.SHOW_LIGHT_TRANSACTIONS; @@ -89,7 +83,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { boolean mPolicyVisibilityAfterAnim = true; boolean mAppFreezing; boolean mAttachedHidden; // is our parent window hidden? - boolean mLastHidden; // was this window last hidden? boolean mWallpaperVisible; // for wallpaper, what was last vis report? /** @@ -212,24 +205,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { // when in that case until the layout is done. boolean mLayoutNeeded; - // This is set after the Surface has been created but before the - // window has been drawn. During this time the surface is hidden. - boolean mDrawPending; - - // This is set after the window has finished drawing for the first - // time but before its surface is shown. The surface will be - // displayed when the next layout is run. - boolean mCommitDrawPending; - - // This is set during the time after the window's drawing has been - // committed, and before its surface is actually shown. It is used - // to delay showing the surface until all windows in a token are ready - // to be shown. - boolean mReadyToShow; - - // Set when the window has been shown in the screen the first time. - boolean mHasDrawn; - // Currently running an exit animation? boolean mExiting; @@ -261,6 +236,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { final WindowStateAnimator mWinAnimator; + boolean mHasSurface = false; + WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, WindowState attachedWindow, int seq, WindowManager.LayoutParams a, int viewVisibility) { @@ -276,7 +253,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { mSeq = seq; mEnforceSizeCompat = (mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0; if (WindowManagerService.localLOGV) Slog.v( - WindowManagerService.TAG, "Window " + this + " client=" + c.asBinder() + TAG, "Window " + this + " client=" + c.asBinder() + " token=" + token + " (" + mAttrs.token + ")"); try { c.asBinder().linkToDeath(deathRecipient, 0); @@ -304,7 +281,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { + WindowManagerService.TYPE_LAYER_OFFSET; mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type); mAttachedWindow = attachedWindow; - if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(WindowManagerService.TAG, "Adding " + this + " to " + mAttachedWindow); + if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + mAttachedWindow); mAttachedWindow.mChildWindows.add(this); mLayoutAttached = mAttrs.type != WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; @@ -358,7 +335,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { void attach() { if (WindowManagerService.localLOGV) Slog.v( - WindowManagerService.TAG, "Attaching " + this + " token=" + mToken + TAG, "Attaching " + this + " token=" + mToken + ", list=" + mToken.windows); mSession.windowAddedLocked(); } @@ -496,7 +473,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { if (WindowManagerService.localLOGV) { //if ("com.google.android.youtube".equals(mAttrs.packageName) // && mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) { - Slog.v(WindowManagerService.TAG, "Resolving (mRequestedWidth=" + Slog.v(TAG, "Resolving (mRequestedWidth=" + mRequestedWidth + ", mRequestedheight=" + mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph + "): frame=" + mFrame.toShortString() @@ -600,34 +577,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { return mAppToken != null ? mAppToken.firstWindowDrawn : false; } - boolean finishDrawingLocked() { - if (mDrawPending) { - if (SHOW_TRANSACTIONS || WindowManagerService.DEBUG_ORIENTATION) Slog.v( - WindowManagerService.TAG, "finishDrawingLocked: " + this + " in " - + mWinAnimator.mSurface); - mCommitDrawPending = true; - mDrawPending = false; - return true; - } - return false; - } - - // This must be called while inside a transaction. - boolean commitFinishDrawingLocked(long currentTime) { - //Slog.i(TAG, "commitFinishDrawingLocked: " + mSurface); - if (!mCommitDrawPending) { - return false; - } - mCommitDrawPending = false; - mReadyToShow = true; - final boolean starting = mAttrs.type == TYPE_APPLICATION_STARTING; - final AppWindowToken atoken = mAppToken; - if (atoken == null || atoken.allDrawn || starting) { - mWinAnimator.performShowLocked(); - } - return true; - } - boolean isIdentityMatrix(float dsdx, float dtdx, float dsdy, float dtdy) { if (dsdx < .99999f || dsdx > 1.00001f) return false; if (dtdy < .99999f || dtdy > 1.00001f) return false; @@ -652,7 +601,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { */ public boolean isVisibleLw() { final AppWindowToken atoken = mAppToken; - return mWinAnimator.mSurface != null && mPolicyVisibility && !mAttachedHidden + return mHasSurface && mPolicyVisibility && !mAttachedHidden && (atoken == null || !atoken.hiddenRequested) && !mExiting && !mDestroying; } @@ -673,7 +622,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { final AppWindowToken atoken = mAppToken; final boolean animating = atoken != null ? (atoken.animation != null) : false; - return mWinAnimator.mSurface != null && !mDestroying && !mExiting + return mHasSurface && !mDestroying && !mExiting && (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested) && ((!mAttachedHidden && mViewVisibility == View.VISIBLE && !mRootToken.hidden) @@ -687,7 +636,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { */ public boolean isWinVisibleLw() { final AppWindowToken atoken = mAppToken; - return mWinAnimator.mSurface != null && mPolicyVisibility && !mAttachedHidden + return mHasSurface && mPolicyVisibility && !mAttachedHidden && (atoken == null || !atoken.hiddenRequested || atoken.animating) && !mExiting && !mDestroying; } @@ -697,7 +646,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { * the associated app token, not the pending requested hidden state. */ boolean isVisibleNow() { - return mWinAnimator.mSurface != null && mPolicyVisibility && !mAttachedHidden + return mHasSurface && mPolicyVisibility && !mAttachedHidden && !mRootToken.hidden && !mExiting && !mDestroying; } @@ -717,7 +666,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { */ boolean isVisibleOrAdding() { final AppWindowToken atoken = mAppToken; - return ((mWinAnimator.mSurface != null && !mWinAnimator.mReportDestroySurface) + return ((mHasSurface && !mWinAnimator.mReportDestroySurface) || (!mRelayoutCalled && mViewVisibility == View.VISIBLE)) && mPolicyVisibility && !mAttachedHidden && (atoken == null || !atoken.hiddenRequested) @@ -730,15 +679,15 @@ final class WindowState implements WindowManagerPolicy.WindowState { * being visible. */ boolean isOnScreen() { + if (!mHasSurface || !mPolicyVisibility || mDestroying) { + return false; + } final AppWindowToken atoken = mAppToken; if (atoken != null) { - return mWinAnimator.mSurface != null && mPolicyVisibility && !mDestroying - && ((!mAttachedHidden && !atoken.hiddenRequested) + return ((!mAttachedHidden && !atoken.hiddenRequested) || mWinAnimator.mAnimation != null || atoken.animation != null); - } else { - return mWinAnimator.mSurface != null && mPolicyVisibility && !mDestroying - && (!mAttachedHidden || mWinAnimator.mAnimation != null); } + return !mAttachedHidden || mWinAnimator.mAnimation != null; } /** @@ -750,7 +699,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { mService.mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) { return false; } - return mWinAnimator.mSurface != null && mPolicyVisibility && !mDestroying + return mHasSurface && mPolicyVisibility && !mDestroying && ((!mAttachedHidden && mViewVisibility == View.VISIBLE && !mRootToken.hidden) || mWinAnimator.mAnimation != null @@ -784,8 +733,9 @@ final class WindowState implements WindowManagerPolicy.WindowState { * complete UI in to. */ public boolean isDrawnLw() { - return mWinAnimator.mSurface != null && !mDestroying - && !mDrawPending && !mCommitDrawPending; + return mHasSurface && !mDestroying && + (mWinAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW + || mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN); } /** @@ -805,7 +755,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { * sense to call from performLayoutAndPlaceSurfacesLockedInner().) */ boolean shouldAnimateMove() { - return mContentChanged && !mExiting && !mLastHidden && mService.okToDisplay() + return mContentChanged && !mExiting && !mWinAnimator.mLastHidden && mService.okToDisplay() && (mFrame.top != mLastFrame.top || mFrame.left != mLastFrame.left) && (mAttachedWindow == null || !mAttachedWindow.shouldAnimateMove()); @@ -820,7 +770,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { disposeInputChannel(); if (mAttachedWindow != null) { - if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(WindowManagerService.TAG, "Removing " + this + " from " + mAttachedWindow); + if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing " + this + " from " + mAttachedWindow); mAttachedWindow.mChildWindows.remove(this); } mWinAnimator.destroyDeferredSurfaceLocked(); @@ -859,7 +809,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { try { synchronized(mService.mWindowMap) { WindowState win = mService.windowForClientLocked(mSession, mClient, false); - Slog.i(WindowManagerService.TAG, "WIN DEATH: " + win); + Slog.i(TAG, "WIN DEATH: " + win); if (win != null) { mService.removeWindowLocked(mSession, win); } @@ -878,10 +828,12 @@ final class WindowState implements WindowManagerPolicy.WindowState { && ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0); } + @Override public boolean hasDrawnLw() { - return mHasDrawn; + return mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN; } + @Override public boolean showLw(boolean doAnimation) { return showLw(doAnimation, true); } @@ -891,9 +843,9 @@ final class WindowState implements WindowManagerPolicy.WindowState { // Already showing. return false; } - if (DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "Policy visibility true: " + this); + if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility true: " + this); if (doAnimation) { - if (DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "doAnimation: mPolicyVisibility=" + if (DEBUG_VISIBILITY) Slog.v(TAG, "doAnimation: mPolicyVisibility=" + mPolicyVisibility + " mAnimation=" + mWinAnimator.mAnimation); if (!mService.okToDisplay()) { doAnimation = false; @@ -940,7 +892,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { if (doAnimation) { mPolicyVisibilityAfterAnim = false; } else { - if (DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "Policy visibility false: " + this); + if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility false: " + this); mPolicyVisibilityAfterAnim = false; mPolicyVisibility = false; // Window is no longer visible -- make sure if we were waiting @@ -1026,7 +978,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { } pw.print(prefix); pw.print("mViewVisibility=0x"); pw.print(Integer.toHexString(mViewVisibility)); - pw.print(" mLastHidden="); pw.print(mLastHidden); pw.print(" mHaveFrame="); pw.print(mHaveFrame); pw.print(" mObscured="); pw.println(mObscured); pw.print(prefix); pw.print("mSeq="); pw.print(mSeq); @@ -1060,8 +1011,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { } pw.print(prefix); pw.print("mConfiguration="); pw.println(mConfiguration); } - pw.print(prefix); pw.print("mShownFrame="); - mShownFrame.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface); + pw.print(" mShownFrame="); mShownFrame.printShortString(pw); pw.println(); if (dumpAll) { pw.print(prefix); pw.print("mFrame="); mFrame.printShortString(pw); pw.print(" last="); mLastFrame.printShortString(pw); @@ -1089,12 +1040,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { pw.println(); } mWinAnimator.dump(pw, prefix, dumpAll); - if (dumpAll) { - pw.print(prefix); pw.print("mDrawPending="); pw.print(mDrawPending); - pw.print(" mCommitDrawPending="); pw.print(mCommitDrawPending); - pw.print(" mReadyToShow="); pw.print(mReadyToShow); - pw.print(" mHasDrawn="); pw.println(mHasDrawn); - } if (mExiting || mRemoveOnExit || mDestroying || mRemoved) { pw.print(prefix); pw.print("mExiting="); pw.print(mExiting); pw.print(" mRemoveOnExit="); pw.print(mRemoveOnExit); diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java index 789e74b46f01..4979a4c31b5b 100644 --- a/services/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/java/com/android/server/wm/WindowStateAnimator.java @@ -9,6 +9,7 @@ import android.content.Context; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Rect; +import android.graphics.Region; import android.os.RemoteException; import android.util.Slog; import android.view.Surface; @@ -35,6 +36,7 @@ class WindowStateAnimator { static final boolean SHOW_LIGHT_TRANSACTIONS = WindowManagerService.SHOW_LIGHT_TRANSACTIONS; static final boolean SHOW_SURFACE_ALLOC = WindowManagerService.SHOW_SURFACE_ALLOC; static final boolean localLOGV = WindowManagerService.localLOGV; + static final boolean DEBUG_ORIENTATION = WindowManagerService.DEBUG_ORIENTATION; static final String TAG = "WindowStateAnimator"; @@ -98,6 +100,25 @@ class WindowStateAnimator { // an enter animation. boolean mEnterAnimationPending; + /** This is set when there is no Surface */ + static final int NO_SURFACE = 0; + /** This is set after the Surface has been created but before the window has been drawn. During + * this time the surface is hidden. */ + static final int DRAW_PENDING = 1; + /** This is set after the window has finished drawing for the first time but before its surface + * is shown. The surface will be displayed when the next layout is run. */ + static final int COMMIT_DRAW_PENDING = 2; + /** This is set during the time after the window's drawing has been committed, and before its + * surface is actually shown. It is used to delay showing the surface until all windows in a + * token are ready to be shown. */ + static final int READY_TO_SHOW = 3; + /** Set when the window has been shown in the screen the first time. */ + static final int HAS_DRAWN = 4; + int mDrawState; + + /** Was this window last hidden? */ + boolean mLastHidden; + public WindowStateAnimator(final WindowManagerService service, final WindowState win, final WindowState attachedWindow) { mService = service; @@ -119,7 +140,7 @@ class WindowStateAnimator { mAnimation.scaleCurrentDuration(mService.mWindowAnimationScale); // Start out animation gone if window is gone, or visible if window is visible. mTransformation.clear(); - mTransformation.setAlpha(mWin.mLastHidden ? 0 : 1); + mTransformation.setAlpha(mLastHidden ? 0 : 1); mHasLocalTransformation = true; } @@ -149,13 +170,11 @@ class WindowStateAnimator { } void cancelExitAnimationForNextAnimationLocked() { - if (!mWin.mExiting) return; if (mAnimation != null) { mAnimation.cancel(); mAnimation = null; destroySurfaceLocked(); } - mWin.mExiting = false; } private boolean stepAnimation(long currentTime) { @@ -282,7 +301,7 @@ class WindowStateAnimator { } } mTransformation.clear(); - if (mWin.mHasDrawn + if (mDrawState == HAS_DRAWN && mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING && mWin.mAppToken != null && mWin.mAppToken.firstWindowDrawn @@ -338,7 +357,7 @@ class WindowStateAnimator { } catch (RuntimeException e) { Slog.w(TAG, "Error hiding surface in " + this, e); } - mWin.mLastHidden = true; + mLastHidden = true; } mWin.mExiting = false; if (mWin.mRemoveOnExit) { @@ -347,15 +366,38 @@ class WindowStateAnimator { } } + boolean finishDrawingLocked() { + if (mDrawState == DRAW_PENDING) { + if (SHOW_TRANSACTIONS || DEBUG_ORIENTATION) Slog.v( + TAG, "finishDrawingLocked: " + this + " in " + mSurface); + mDrawState = COMMIT_DRAW_PENDING; + return true; + } + return false; + } + + // This must be called while inside a transaction. + boolean commitFinishDrawingLocked(long currentTime) { + //Slog.i(TAG, "commitFinishDrawingLocked: " + mSurface); + if (mDrawState != COMMIT_DRAW_PENDING) { + return false; + } + mDrawState = READY_TO_SHOW; + final boolean starting = mWin.mAttrs.type == TYPE_APPLICATION_STARTING; + final AppWindowToken atoken = mWin.mAppToken; + if (atoken == null || atoken.allDrawn || starting) { + performShowLocked(); + } + return true; + } + Surface createSurfaceLocked() { if (mSurface == null) { mReportDestroySurface = false; mSurfacePendingDestroy = false; - if (WindowManagerService.DEBUG_ORIENTATION) Slog.i(TAG, + if (DEBUG_ORIENTATION) Slog.i(TAG, "createSurface " + this + ": DRAW NOW PENDING"); - mWin.mDrawPending = true; - mWin.mCommitDrawPending = false; - mWin.mReadyToShow = false; + mDrawState = DRAW_PENDING; if (mWin.mAppToken != null) { mWin.mAppToken.allDrawn = false; } @@ -407,6 +449,7 @@ class WindowStateAnimator { mSession.mSurfaceSession, mSession.mPid, attrs.getTitle().toString(), 0, w, h, format, flags); + mWin.mHasSurface = true; if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG, " CREATE SURFACE " + mSurface + " IN SESSION " @@ -416,11 +459,15 @@ class WindowStateAnimator { + Integer.toHexString(flags) + " / " + this); } catch (Surface.OutOfResourcesException e) { + mWin.mHasSurface = false; Slog.w(TAG, "OutOfResourcesException creating surface"); mService.reclaimSomeSurfaceMemoryLocked(this, "create", true); + mDrawState = NO_SURFACE; return null; } catch (Exception e) { + mWin.mHasSurface = false; Slog.e(TAG, "Exception creating surface", e); + mDrawState = NO_SURFACE; return null; } @@ -453,7 +500,7 @@ class WindowStateAnimator { Slog.w(TAG, "Error creating surface in " + w, e); mService.reclaimSomeSurfaceMemoryLocked(this, "create-init", true); } - mWin.mLastHidden = true; + mLastHidden = true; } finally { Surface.closeTransaction(); if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, @@ -470,10 +517,8 @@ class WindowStateAnimator { mWin.mAppToken.startingDisplayed = false; } + mDrawState = NO_SURFACE; if (mSurface != null) { - mWin.mDrawPending = false; - mWin.mCommitDrawPending = false; - mWin.mReadyToShow = false; int i = mWin.mChildWindows.size(); while (i > 0) { @@ -537,6 +582,7 @@ class WindowStateAnimator { mSurfaceShown = false; mSurface = null; + mWin.mHasSurface =false; } } @@ -554,7 +600,7 @@ class WindowStateAnimator { mPendingDestroySurface.destroy(); } } catch (RuntimeException e) { - Slog.w(WindowManagerService.TAG, "Exception thrown when destroying Window " + Slog.w(TAG, "Exception thrown when destroying Window " + this + " surface " + mPendingDestroySurface + " session " + mSession + ": " + e.toString()); } @@ -705,11 +751,12 @@ class WindowStateAnimator { mDsDy = 0; mDtDy = mWin.mGlobalScale; } + public void prepareSurfaceLocked(final boolean recoveringMemory) { final WindowState w = mWin; if (mSurface == null) { if (w.mOrientationChanging) { - if (WindowManagerService.DEBUG_ORIENTATION) { + if (DEBUG_ORIENTATION) { Slog.v(TAG, "Orientation change skips hidden " + w); } w.mOrientationChanging = false; @@ -782,9 +829,9 @@ class WindowStateAnimator { } if (w.mAttachedHidden || !w.isReadyForDisplay()) { - if (!w.mLastHidden) { + if (!mLastHidden) { //dump(); - w.mLastHidden = true; + mLastHidden = true; if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w, "HIDE (performLayout)", null); if (mSurface != null) { @@ -804,7 +851,7 @@ class WindowStateAnimator { // new orientation. if (w.mOrientationChanging) { w.mOrientationChanging = false; - if (WindowManagerService.DEBUG_ORIENTATION) Slog.v(TAG, + if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation change skips hidden " + w); } } else if (mLastLayer != mAnimLayer @@ -815,7 +862,7 @@ class WindowStateAnimator { || mLastDtDy != mDtDy || w.mLastHScale != w.mHScale || w.mLastVScale != w.mVScale - || w.mLastHidden) { + || mLastHidden) { displayed = true; mLastAlpha = mShownAlpha; mLastLayer = mAnimLayer; @@ -840,6 +887,21 @@ class WindowStateAnimator { mSurface.setMatrix( mDsDx*w.mHScale, mDtDx*w.mVScale, mDsDy*w.mHScale, mDtDy*w.mVScale); + + if (mLastHidden && mDrawState == HAS_DRAWN) { + if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w, + "SHOW (performLayout)", null); + if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + w + + " during relayout"); + if (showSurfaceRobustlyLocked()) { + mLastHidden = false; + } else { + w.mOrientationChanging = false; + } + } + if (mSurface != null) { + w.mToken.hasVisible = true; + } } catch (RuntimeException e) { Slog.w(TAG, "Error updating surface in " + w, e); if (!recoveringMemory) { @@ -847,23 +909,6 @@ class WindowStateAnimator { } } } - - if (w.mLastHidden && w.isDrawnLw() - && !w.mReadyToShow) { - if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w, - "SHOW (performLayout)", null); - if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + w - + " during relayout"); - if (showSurfaceRobustlyLocked()) { - w.mHasDrawn = true; - w.mLastHidden = false; - } else { - w.mOrientationChanging = false; - } - } - if (mSurface != null) { - w.mToken.hasVisible = true; - } } else { displayed = true; } @@ -872,18 +917,45 @@ class WindowStateAnimator { if (w.mOrientationChanging) { if (!w.isDrawnLw()) { mService.mInnerFields.mOrientationChangeComplete = false; - if (WindowManagerService.DEBUG_ORIENTATION) Slog.v(TAG, + if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation continue waiting for draw in " + w); } else { w.mOrientationChanging = false; - if (WindowManagerService.DEBUG_ORIENTATION) Slog.v(TAG, - "Orientation change complete in " + w); + if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation change complete in " + w); } } w.mToken.hasVisible = true; } } + void setTransparentRegionHint(final Region region) { + if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, + ">>> OPEN TRANSACTION setTransparentRegion"); + Surface.openTransaction(); + try { + if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin, + "transparentRegionHint=" + region, null); + mSurface.setTransparentRegionHint(region); + } finally { + Surface.closeTransaction(); + if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, + "<<< CLOSE TRANSACTION setTransparentRegion"); + } + } + + void setWallpaperOffset(int left, int top) { + Surface.openTransaction(); + try { + mSurfaceX = left; + mSurfaceY = top; + mSurface.setPosition(left, top); + } catch (RuntimeException e) { + Slog.w(TAG, "Error positioning surface of " + mWin + + " pos=(" + left + "," + top + ")", e); + } + Surface.closeTransaction(); + } + // This must be called while inside a transaction. boolean performShowLocked() { if (DEBUG_VISIBILITY) { @@ -892,15 +964,15 @@ class WindowStateAnimator { e = new RuntimeException(); e.fillInStackTrace(); } - Slog.v(WindowManagerService.TAG, "performShow on " + this - + ": readyToShow=" + mWin.mReadyToShow + " readyForDisplay=" + Slog.v(TAG, "performShow on " + this + + ": mDrawState=" + mDrawState + " readyForDisplay=" + mWin.isReadyForDisplay() + " starting=" + (mWin.mAttrs.type == TYPE_APPLICATION_STARTING), e); } - if (mWin.mReadyToShow && mWin.isReadyForDisplay()) { - if (SHOW_TRANSACTIONS || WindowManagerService.DEBUG_ORIENTATION) + if (mDrawState == READY_TO_SHOW && mWin.isReadyForDisplay()) { + if (SHOW_TRANSACTIONS || DEBUG_ORIENTATION) WindowManagerService.logSurface(mWin, "SHOW (performShowLocked)", null); - if (DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "Showing " + this + if (DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + this + " during animation: policyVis=" + mWin.mPolicyVisibility + " attHidden=" + mWin.mAttachedHidden + " tok.hiddenRequested=" @@ -919,9 +991,8 @@ class WindowStateAnimator { applyEnterAnimationLocked(); mLastAlpha = -1; - mWin.mHasDrawn = true; - mWin.mLastHidden = false; - mWin.mReadyToShow = false; + mLastHidden = false; + mDrawState = HAS_DRAWN; int i = mWin.mChildWindows.size(); while (i > 0) { @@ -947,7 +1018,7 @@ class WindowStateAnimator { if (mWin.mAppToken.startingData != null) { if (WindowManagerService.DEBUG_STARTING_WINDOW || - WindowManagerService.DEBUG_ANIM) Slog.v(WindowManagerService.TAG, + WindowManagerService.DEBUG_ANIM) Slog.v(TAG, "Finish starting " + mWin.mToken + ": first real window is shown, no animation"); // If this initial window is animating, stop it -- we @@ -1014,6 +1085,7 @@ class WindowStateAnimator { applyAnimationLocked(transit, true); } + // TODO(cmautner): Move back to WindowState? /** * Choose the correct animation and set it to the passed WindowState. * @param transit If WindowManagerPolicy.TRANSIT_PREVIEW_DONE and the app window has been drawn @@ -1059,7 +1131,7 @@ class WindowStateAnimator { a = mService.loadAnimation(mWin.mAttrs, attr); } } - if (WindowManagerService.DEBUG_ANIM) Slog.v(WindowManagerService.TAG, + if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG, "applyAnimation: win=" + this + " anim=" + anim + " attr=0x" + Integer.toHexString(attr) + " mAnimation=" + mAnimation @@ -1071,7 +1143,7 @@ class WindowStateAnimator { e = new RuntimeException(); e.fillInStackTrace(); } - Slog.v(WindowManagerService.TAG, "Loaded animation " + a + " for " + this, e); + Slog.v(TAG, "Loaded animation " + a + " for " + this, e); } setAnimation(a); mAnimationIsEntrance = isEntrance; @@ -1101,6 +1173,8 @@ class WindowStateAnimator { if (mSurface != null) { if (dumpAll) { pw.print(prefix); pw.print("mSurface="); pw.println(mSurface); + pw.print(prefix); pw.print("mDrawState="); pw.print(mDrawState); + pw.print(" mLastHidden="); pw.println(mLastHidden); } pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown); pw.print(" layer="); pw.print(mSurfaceLayer); @@ -1132,4 +1206,12 @@ class WindowStateAnimator { } } + @Override + public String toString() { + StringBuffer sb = new StringBuffer("WindowStateAnimator ("); + sb.append(mWin.mLastTitle + "): "); + sb.append("mSurface " + mSurface); + sb.append(", mAnimation " + mAnimation); + return sb.toString(); + } } diff --git a/telephony/java/com/android/internal/telephony/ApnContext.java b/telephony/java/com/android/internal/telephony/ApnContext.java index 8aeee87c7be0..80d5044276be 100644 --- a/telephony/java/com/android/internal/telephony/ApnContext.java +++ b/telephony/java/com/android/internal/telephony/ApnContext.java @@ -17,6 +17,9 @@ package com.android.internal.telephony; import android.util.Log; + +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -207,4 +210,19 @@ public class ApnContext { protected void log(String s) { Log.d(LOG_TAG, "[ApnContext] " + s); } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("ApnContext:"); + pw.println(" mApnType=" + mApnType); + pw.println(" mState=" + mState); + pw.println(" mWaitingApns=" + mWaitingApns); + pw.println(" mWaitingApnsPermanentFailureCountDown=" + + mWaitingApnsPermanentFailureCountDown); + pw.println(" mApnSetting=" + mApnSetting); + pw.println(" mDataConnection=" + mDataConnection); + pw.println(" mDataConnectionAc=" + mDataConnectionAc); + pw.println(" mReason=" + mReason); + pw.println(" mDataEnabled=" + mDataEnabled); + pw.println(" mDependencyMet=" + mDependencyMet); + } } diff --git a/telephony/java/com/android/internal/telephony/CallTracker.java b/telephony/java/com/android/internal/telephony/CallTracker.java index 958481cb7f43..62caf01df600 100644 --- a/telephony/java/com/android/internal/telephony/CallTracker.java +++ b/telephony/java/com/android/internal/telephony/CallTracker.java @@ -25,6 +25,9 @@ import android.util.Log; import com.android.internal.telephony.CommandException; +import java.io.FileDescriptor; +import java.io.PrintWriter; + /** * {@hide} @@ -170,4 +173,10 @@ public abstract class CallTracker extends Handler { protected abstract void log(String msg); + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("CallTracker:"); + pw.println(" pendingOperations=" + pendingOperations); + pw.println(" needsPoll=" + needsPoll); + pw.println(" lastRelevantPoll=" + lastRelevantPoll); + } } diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java index 238afbed320f..486a924064a4 100644 --- a/telephony/java/com/android/internal/telephony/DataConnection.java +++ b/telephony/java/com/android/internal/telephony/DataConnection.java @@ -32,6 +32,8 @@ import android.os.Message; import android.os.SystemProperties; import android.text.TextUtils; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -1185,4 +1187,27 @@ public abstract class DataConnection extends StateMachine { sendMessage(obtainMessage(EVENT_DISCONNECT_ALL, new DisconnectParams(reason, onCompletedMsg))); } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("DataConnection name=" + getName() + ":"); + pw.println(" mApnList=" + mApnList); + pw.println(" mDataConnectionTracker=" + mDataConnectionTracker); + pw.println(" mApn=" + mApn); + pw.println(" mTag=" + mTag); + pw.println(" phone=" + phone); + pw.println(" mRilVersion=" + mRilVersion); + pw.println(" cid=" + cid); + pw.println(" mLinkProperties=" + mLinkProperties); + pw.println(" mCapabilities=" + mCapabilities); + pw.println(" createTime=" + createTime); + pw.println(" lastFailTime=" + lastFailTime); + pw.println(" lastFailCause=" + lastFailCause); + pw.println(" mRetryOverride=" + mRetryOverride); + pw.println(" mRefCount=" + mRefCount); + pw.println(" userData=" + userData); + pw.println(" total messages=" + getProcessedMessagesCount()); + for (int i=0; i < getProcessedMessagesSize(); i++) { + pw.printf(" msg[%d]=%s\n", i, getProcessedMessageInfo(i)); + } + } } diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java index fa90f1c3a4c8..55f2ca3c4541 100644 --- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java @@ -49,8 +49,12 @@ import com.android.internal.telephony.DataConnection.FailCause; import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; +import java.util.Map.Entry; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; @@ -1193,4 +1197,80 @@ public abstract class DataConnectionTracker extends Handler { dc.resetRetryCount(); } } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("DataConnectionTracker:"); + pw.println(" mInternalDataEnabled=" + mInternalDataEnabled); + pw.println(" mUserDataEnabled=" + mUserDataEnabled); + pw.println(" sPolicyDataEnabed=" + sPolicyDataEnabled); + pw.println(" dataEnabled:"); + for(int i=0; i < dataEnabled.length; i++) { + pw.printf(" dataEnabled[%d]=%b\n", i, dataEnabled[i]); + } + pw.flush(); + pw.println(" enabledCount=" + enabledCount); + pw.println(" mRequestedApnType=" + mRequestedApnType); + pw.println(" mPhone=" + mPhone.getPhoneName()); + pw.println(" mActivity=" + mActivity); + pw.println(" mState=" + mState); + pw.println(" mTxPkts=" + mTxPkts); + pw.println(" mRxPkts=" + mRxPkts); + pw.println(" mNetStatPollPeriod=" + mNetStatPollPeriod); + pw.println(" mNetStatPollEnabled=" + mNetStatPollEnabled); + pw.println(" mDataStallTxRxSum=" + mDataStallTxRxSum); + pw.println(" mDataStallAlarmTag=" + mDataStallAlarmTag); + pw.println(" mSentSinceLastRecv=" + mSentSinceLastRecv); + pw.println(" mNoRecvPollCount=" + mNoRecvPollCount); + pw.println(" mIsWifiConnected=" + mIsWifiConnected); + pw.println(" mReconnectIntent=" + mReconnectIntent); + pw.println(" mCidActive=" + mCidActive); + pw.println(" mAutoAttachOnCreation=" + mAutoAttachOnCreation); + pw.println(" mIsScreenOn=" + mIsScreenOn); + pw.println(" mUniqueIdGenerator=" + mUniqueIdGenerator); + pw.flush(); + pw.println(" ***************************************"); + Set<Entry<Integer, DataConnection> > mDcSet = mDataConnections.entrySet(); + pw.println(" mDataConnections: count=" + mDcSet.size()); + for (Entry<Integer, DataConnection> entry : mDcSet) { + pw.printf(" *** mDataConnection[%d] \n", entry.getKey()); + entry.getValue().dump(fd, pw, args); + } + pw.println(" ***************************************"); + pw.flush(); + Set<Entry<String, Integer>> mApnToDcIdSet = mApnToDataConnectionId.entrySet(); + pw.println(" mApnToDataConnectonId size=" + mApnToDcIdSet.size()); + for (Entry<String, Integer> entry : mApnToDcIdSet) { + pw.printf(" mApnToDataConnectonId[%s]=%d\n", entry.getKey(), entry.getValue()); + } + pw.println(" ***************************************"); + pw.flush(); + if (mApnContexts != null) { + Set<Entry<String, ApnContext>> mApnContextsSet = mApnContexts.entrySet(); + pw.println(" mApnContexts size=" + mApnContextsSet.size()); + for (Entry<String, ApnContext> entry : mApnContextsSet) { + pw.printf(" *** mApnContexts[%s]:\n", entry.getKey()); + entry.getValue().dump(fd, pw, args); + } + pw.println(" ***************************************"); + } else { + pw.println(" mApnContexts=null"); + } + pw.flush(); + pw.println(" mActiveApn=" + mActiveApn); + if (mAllApns != null) { + pw.println(" mAllApns size=" + mAllApns.size()); + for (int i=0; i < mAllApns.size(); i++) { + pw.printf(" mAllApns[%d]: %s\n", i, mAllApns.get(i)); + } + pw.flush(); + } else { + pw.println(" mAllApns=null"); + } + pw.println(" mPreferredApn=" + mPreferredApn); + pw.println(" mIsPsRestricted=" + mIsPsRestricted); + pw.println(" mIsDisposed=" + mIsDisposed); + pw.println(" mIntentReceiver=" + mIntentReceiver); + pw.println(" mDataRoamingSettingObserver=" + mDataRoamingSettingObserver); + pw.flush(); + } } diff --git a/telephony/java/com/android/internal/telephony/DebugService.java b/telephony/java/com/android/internal/telephony/DebugService.java new file mode 100644 index 000000000000..29fea6e83fcd --- /dev/null +++ b/telephony/java/com/android/internal/telephony/DebugService.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.telephony; + +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * A debug service that will dump telephony's state + * + * Currently this "Service" has a proxy in the phone app + * com.android.phone.TelephonyDebugService which actually + * invokes the dump method. + */ +public class DebugService { + private static String TAG = "DebugService"; + + /** Constructor */ + public DebugService() { + log("DebugService:"); + } + + /** + * Dump the state of various objects, add calls to other objects as desired. + */ + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + log("dump: +"); + PhoneProxy phoneProxy = null; + PhoneBase phoneBase = null; + + try { + phoneProxy = (PhoneProxy) PhoneFactory.getDefaultPhone(); + } catch (Exception e) { + pw.println("Telephony DebugService: Could not getDefaultPhone e=" + e); + return; + } + try { + phoneBase = (PhoneBase)phoneProxy.getActivePhone(); + } catch (Exception e) { + pw.println("Telephony DebugService: Could not PhoneBase e=" + e); + return; + } + + /** + * Surround each of the sub dump's with try/catch so even + * if one fails we'll be able to dump the next ones. + */ + pw.println(); + pw.println("++++++++++++++++++++++++++++++++"); + pw.flush(); + try { + phoneBase.dump(fd, pw, args); + } catch (Exception e) { + e.printStackTrace(); + } + pw.flush(); + pw.println("++++++++++++++++++++++++++++++++"); + try { + phoneBase.mDataConnectionTracker.dump(fd, pw, args); + } catch (Exception e) { + e.printStackTrace(); + } + pw.flush(); + pw.println("++++++++++++++++++++++++++++++++"); + try { + phoneBase.getServiceStateTracker().dump(fd, pw, args); + } catch (Exception e) { + e.printStackTrace(); + } + pw.flush(); + pw.println("++++++++++++++++++++++++++++++++"); + try { + phoneBase.getCallTracker().dump(fd, pw, args); + } catch (Exception e) { + e.printStackTrace(); + } + pw.flush(); + pw.println("++++++++++++++++++++++++++++++++"); + try { + ((RIL)phoneBase.mCM).dump(fd, pw, args); + } catch (Exception e) { + e.printStackTrace(); + } + pw.flush(); + pw.println("++++++++++++++++++++++++++++++++"); + log("dump: -"); + } + + private static void log(String s) { + Log.d(TAG, "DebugService " + s); + } +} diff --git a/telephony/java/com/android/internal/telephony/PhoneBase.java b/telephony/java/com/android/internal/telephony/PhoneBase.java index 3aa53eefdd90..1b4cb157c61e 100644 --- a/telephony/java/com/android/internal/telephony/PhoneBase.java +++ b/telephony/java/com/android/internal/telephony/PhoneBase.java @@ -42,6 +42,8 @@ import com.android.internal.telephony.ims.IsimRecords; import com.android.internal.telephony.test.SimulatedRadioControl; import com.android.internal.telephony.gsm.SIMRecords; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.Locale; import java.util.concurrent.atomic.AtomicReference; @@ -1144,4 +1146,43 @@ public abstract class PhoneBase extends Handler implements Phone { public UsimServiceTable getUsimServiceTable() { return mIccRecords.getUsimServiceTable(); } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("PhoneBase:"); + pw.println(" mCM=" + mCM); + pw.println(" mDnsCheckDisabled=" + mDnsCheckDisabled); + pw.println(" mDataConnectionTracker=" + mDataConnectionTracker); + pw.println(" mDoesRilSendMultipleCallRing=" + mDoesRilSendMultipleCallRing); + pw.println(" mCallRingContinueToken=" + mCallRingContinueToken); + pw.println(" mCallRingDelay=" + mCallRingDelay); + pw.println(" mIsTheCurrentActivePhone=" + mIsTheCurrentActivePhone); + pw.println(" mIsVoiceCapable=" + mIsVoiceCapable); + pw.println(" mIccRecords=" + mIccRecords); + pw.println(" mIccCard=" + mIccCard.get()); + pw.println(" mSmsStorageMonitor=" + mSmsStorageMonitor); + pw.println(" mSmsUsageMonitor=" + mSmsUsageMonitor); + pw.println(" mSMS=" + mSMS); + pw.flush(); + pw.println(" mLooper=" + mLooper); + pw.println(" mContext=" + mContext); + pw.println(" mNotifier=" + mNotifier); + pw.println(" mSimulatedRadioControl=" + mSimulatedRadioControl); + pw.println(" mUnitTestMode=" + mUnitTestMode); + pw.println(" isDnsCheckDisabled()=" + isDnsCheckDisabled()); + pw.println(" getUnitTestMode()=" + getUnitTestMode()); + pw.println(" getState()=" + getState()); + pw.println(" getIccSerialNumber()=" + getIccSerialNumber()); + pw.println(" getIccRecordsLoaded()=" + getIccRecordsLoaded()); + pw.println(" getMessageWaitingIndicator()=" + getMessageWaitingIndicator()); + pw.println(" getCallForwardingIndicator()=" + getCallForwardingIndicator()); + pw.println(" isInEmergencyCall()=" + isInEmergencyCall()); + pw.flush(); + pw.println(" isInEcm()=" + isInEcm()); + pw.println(" getPhoneName()=" + getPhoneName()); + pw.println(" getPhoneType()=" + getPhoneType()); + pw.println(" getVoiceMessageCount()=" + getVoiceMessageCount()); + pw.println(" getActiveApnTypes()=" + getActiveApnTypes()); + pw.println(" isDataConnectivityPossible()=" + isDataConnectivityPossible()); + pw.println(" needsOtaServiceProvisioning=" + needsOtaServiceProvisioning()); + } } diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java index cf96ab28f0df..b14f6c8ea60f 100644 --- a/telephony/java/com/android/internal/telephony/RIL.java +++ b/telephony/java/com/android/internal/telephony/RIL.java @@ -56,8 +56,10 @@ import com.android.internal.telephony.IccRefreshResponse; import java.io.ByteArrayInputStream; import java.io.DataInputStream; +import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.concurrent.atomic.AtomicBoolean; @@ -3833,4 +3835,27 @@ public final class RIL extends BaseCommands implements CommandsInterface { if (RILJ_LOGD) riljLog("testingEmergencyCall"); mTestingEmergencyCall.set(true); } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("RIL:"); + pw.println(" mSocket=" + mSocket); + pw.println(" mSenderThread=" + mSenderThread); + pw.println(" mSender=" + mSender); + pw.println(" mReceiverThread=" + mReceiverThread); + pw.println(" mReceiver=" + mReceiver); + pw.println(" mWakeLock=" + mWakeLock); + pw.println(" mWakeLockTimeout=" + mWakeLockTimeout); + synchronized (mRequestsList) { + pw.println(" mRequestMessagesPending=" + mRequestMessagesPending); + pw.println(" mRequestMessagesWaiting=" + mRequestMessagesWaiting); + int count = mRequestsList.size(); + pw.println(" mRequestList count=" + count); + for (int i = 0; i < count; i++) { + RILRequest rr = mRequestsList.get(i); + pw.println(" [" + rr.mSerial + "] " + requestToString(rr.mRequest)); + } + } + pw.println(" mLastNITZTimeInfo=" + mLastNITZTimeInfo); + pw.println(" mTestingEmergencyCall=" + mTestingEmergencyCall.get()); + } } diff --git a/telephony/java/com/android/internal/telephony/ServiceStateTracker.java b/telephony/java/com/android/internal/telephony/ServiceStateTracker.java index fd39e04ff34a..75eb226d4aa1 100644 --- a/telephony/java/com/android/internal/telephony/ServiceStateTracker.java +++ b/telephony/java/com/android/internal/telephony/ServiceStateTracker.java @@ -24,6 +24,9 @@ import android.os.RegistrantList; import android.telephony.ServiceState; import android.telephony.SignalStrength; +import java.io.FileDescriptor; +import java.io.PrintWriter; + /** * {@hide} */ @@ -459,4 +462,19 @@ public abstract class ServiceStateTracker extends Handler { // This will effectively cancel the rest of the poll requests. pollingContext = new int[1]; } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("ServiceStateTracker:"); + pw.println(" ss=" + ss); + pw.println(" newSS=" + newSS); + pw.println(" mSignalStrength=" + mSignalStrength); + pw.println(" mRestrictedState=" + mRestrictedState); + pw.println(" pollingContext=" + pollingContext); + pw.println(" mDesiredPowerState=" + mDesiredPowerState); + pw.println(" mRilRadioTechnology=" + mRilRadioTechnology); + pw.println(" mNewRilRadioTechnology=" + mNewRilRadioTechnology); + pw.println(" dontPollSignalStrength=" + dontPollSignalStrength); + pw.println(" mPendingRadioPowerOffAfterDataOff=" + mPendingRadioPowerOffAfterDataOff); + pw.println(" mPendingRadioPowerOffAfterDataOffTag=" + mPendingRadioPowerOffAfterDataOffTag); + } } diff --git a/telephony/java/com/android/internal/telephony/TelephonyCapabilities.java b/telephony/java/com/android/internal/telephony/TelephonyCapabilities.java new file mode 100644 index 000000000000..bd94de292d61 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/TelephonyCapabilities.java @@ -0,0 +1,180 @@ +/* + * 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.telephony; + +import android.util.Log; + +import com.android.internal.telephony.Phone; + +/** + * Utilities that check if the phone supports specified capabilities. + */ +public class TelephonyCapabilities { + private static final String LOG_TAG = "TelephonyCapabilities"; + + /** This class is never instantiated. */ + private TelephonyCapabilities() { + } + + /** + * Return true if the current phone supports ECM ("Emergency Callback + * Mode"), which is a feature where the device goes into a special + * state for a short period of time after making an outgoing emergency + * call. + * + * (On current devices, that state lasts 5 minutes. It prevents data + * usage by other apps, to avoid conflicts with any possible incoming + * calls. It also puts up a notification in the status bar, showing a + * countdown while ECM is active, and allowing the user to exit ECM.) + * + * Currently this is assumed to be true for CDMA phones, and false + * otherwise. + */ + public static boolean supportsEcm(Phone phone) { + return (phone.getPhoneType() == Phone.PHONE_TYPE_CDMA); + } + + /** + * Return true if the current phone supports Over The Air Service + * Provisioning (OTASP) + * + * Currently this is assumed to be true for CDMA phones, and false + * otherwise. + * + * TODO: Watch out: this is also highly carrier-specific, since the + * OTASP procedure is different from one carrier to the next, *and* the + * different carriers may want very different onscreen UI as well. + * The procedure may even be different for different devices with the + * same carrier. + * + * So we eventually will need a much more flexible, pluggable design. + * This method here is just a placeholder to reduce hardcoded + * "if (CDMA)" checks sprinkled throughout the phone app. + */ + public static boolean supportsOtasp(Phone phone) { + return (phone.getPhoneType() == Phone.PHONE_TYPE_CDMA); + } + + /** + * Return true if the current phone can retrieve the voice message count. + * + * Currently this is assumed to be true on CDMA phones and false otherwise. + */ + public static boolean supportsVoiceMessageCount(Phone phone) { + return (phone.getPhoneType() == Phone.PHONE_TYPE_CDMA); + } + + /** + * Return true if this phone allows the user to select which + * network to use. + * + * Currently this is assumed to be true only on GSM phones. + * + * TODO: Should CDMA phones allow this as well? + */ + public static boolean supportsNetworkSelection(Phone phone) { + return (phone.getPhoneType() == Phone.PHONE_TYPE_GSM); + } + + /** + * Returns a resource ID for a label to use when displaying the + * "device id" of the current device. (This is currently used as the + * title of the "device id" dialog.) + * + * This is specific to the device's telephony technology: the device + * id is called "IMEI" on GSM phones and "MEID" on CDMA phones. + */ + public static int getDeviceIdLabel(Phone phone) { + if (phone.getPhoneType() == Phone.PHONE_TYPE_GSM) { + return com.android.internal.R.string.imei; + } else if (phone.getPhoneType() == Phone.PHONE_TYPE_CDMA) { + return com.android.internal.R.string.meid; + } else { + Log.w(LOG_TAG, "getDeviceIdLabel: no known label for phone " + + phone.getPhoneName()); + return 0; + } + } + + /** + * Return true if the current phone supports the ability to explicitly + * manage the state of a conference call (i.e. view the participants, + * and hangup or separate individual callers.) + * + * The in-call screen's "Manage conference" UI is available only on + * devices that support this feature. + * + * Currently this is assumed to be true on GSM phones and false otherwise. + */ + public static boolean supportsConferenceCallManagement(Phone phone) { + return ((phone.getPhoneType() == Phone.PHONE_TYPE_GSM) + || (phone.getPhoneType() == Phone.PHONE_TYPE_SIP)); + } + + /** + * Return true if the current phone supports explicit "Hold" and + * "Unhold" actions for an active call. (If so, the in-call UI will + * provide onscreen "Hold" / "Unhold" buttons.) + * + * Currently this is assumed to be true on GSM phones and false + * otherwise. (In particular, CDMA has no concept of "putting a call + * on hold.") + */ + public static boolean supportsHoldAndUnhold(Phone phone) { + return ((phone.getPhoneType() == Phone.PHONE_TYPE_GSM) + || (phone.getPhoneType() == Phone.PHONE_TYPE_SIP)); + } + + /** + * Return true if the current phone supports distinct "Answer & Hold" + * and "Answer & End" behaviors in the call-waiting scenario. If so, + * the in-call UI may provide separate buttons or menu items for these + * two actions. + * + * Currently this is assumed to be true on GSM phones and false + * otherwise. (In particular, CDMA has no concept of explicitly + * managing the background call, or "putting a call on hold.") + * + * TODO: It might be better to expose this capability in a more + * generic form, like maybe "supportsExplicitMultipleLineManagement()" + * rather than focusing specifically on call-waiting behavior. + */ + public static boolean supportsAnswerAndHold(Phone phone) { + return ((phone.getPhoneType() == Phone.PHONE_TYPE_GSM) + || (phone.getPhoneType() == Phone.PHONE_TYPE_SIP)); + } + + /** + * Return true if phones with the given phone type support ADN + * (Abbreviated Dialing Numbers). + * + * Currently this returns true when the phone type is GSM + * ({@link Phone#PHONE_TYPE_GSM}). + * + * This is using int for an argument for letting apps outside + * Phone process access to it, while other methods in this class is + * using Phone object. + * + * TODO: Theoretically phones other than GSM may have the ADN capability. + * Consider having better check here, or have better capability as part + * of public API, with which the argument should be replaced with + * something more appropriate. + */ + public static boolean supportsAdn(int phoneType) { + return phoneType == Phone.PHONE_TYPE_GSM; + } +} diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java index 110d8bf5f8ed..d99a62595f5a 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java +++ b/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java @@ -39,6 +39,9 @@ import com.android.internal.telephony.gsm.SmsMessage; import com.android.internal.telephony.ims.IsimRecords; import com.android.internal.telephony.uicc.UiccController; +import java.io.FileDescriptor; +import java.io.PrintWriter; + public class CDMALTEPhone extends CDMAPhone { static final String LOG_TAG = "CDMA"; @@ -259,4 +262,11 @@ public class CDMALTEPhone extends CDMAPhone { protected void log(String s) { Log.d(LOG_TAG, "[CDMALTEPhone] " + s); } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("CDMALTEPhone extends:"); + super.dump(fd, pw, args); + pw.println(" m3gppSMS=" + m3gppSMS); + } } diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java index bb00d4b96939..ed0081b41d4b 100755 --- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java +++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java @@ -66,6 +66,8 @@ import com.android.internal.telephony.UUSInfo; import com.android.internal.telephony.cat.CatService; import com.android.internal.telephony.uicc.UiccController; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; @@ -81,6 +83,7 @@ import static com.android.internal.telephony.TelephonyProperties.PROPERTY_ICC_OP public class CDMAPhone extends PhoneBase { static final String LOG_TAG = "CDMA"; private static final boolean DBG = true; + private static final boolean VDBG = false; /* STOP SHIP if true */ // Default Emergency Callback Mode exit timer private static final int DEFAULT_ECM_EXIT_TIMER_VALUE = 300000; @@ -1471,4 +1474,32 @@ public class CDMAPhone extends PhoneBase { if (DBG) Log.d(LOG_TAG, "[CDMAPhone] " + s); } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("CDMAPhone extends:"); + super.dump(fd, pw, args); + pw.println(" mVmNumber=" + mVmNumber); + pw.println(" mCT=" + mCT); + pw.println(" mSST=" + mSST); + pw.println(" mCdmaSSM=" + mCdmaSSM); + pw.println(" mPendingMmis=" + mPendingMmis); + pw.println(" mRuimPhoneBookInterfaceManager=" + mRuimPhoneBookInterfaceManager); + pw.println(" mRuimSmsInterfaceManager=" + mRuimSmsInterfaceManager); + pw.println(" mCdmaSubscriptionSource=" + mCdmaSubscriptionSource); + pw.println(" mSubInfo=" + mSubInfo); + pw.println(" mEriManager=" + mEriManager); + pw.println(" mWakeLock=" + mWakeLock); + pw.println(" mIsPhoneInEcmState=" + mIsPhoneInEcmState); + if (VDBG) pw.println(" mImei=" + mImei); + if (VDBG) pw.println(" mImeiSv=" + mImeiSv); + if (VDBG) pw.println(" mEsn=" + mEsn); + if (VDBG) pw.println(" mMeid=" + mMeid); + pw.println(" mCarrierOtaSpNumSchema=" + mCarrierOtaSpNumSchema); + pw.println(" getCdmaEriIconIndex()=" + getCdmaEriIconIndex()); + pw.println(" getCdmaEriIconMode()=" + getCdmaEriIconMode()); + pw.println(" getCdmaEriText()=" + getCdmaEriText()); + pw.println(" isMinInfoReady()=" + isMinInfoReady()); + pw.println(" isCspPlmnEnabled()=" + isCspPlmnEnabled()); + } } diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java index f918dce079c6..af92b08bf236 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaCallTracker.java @@ -34,6 +34,8 @@ import com.android.internal.telephony.DriverCall; import com.android.internal.telephony.Phone; import com.android.internal.telephony.TelephonyProperties; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; @@ -1129,4 +1131,32 @@ public final class CdmaCallTracker extends CallTracker { Log.d(LOG_TAG, "[CdmaCallTracker] " + msg); } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("GsmCallTracker extends:"); + super.dump(fd, pw, args); + pw.println("droppedDuringPoll: length=" + connections.length); + for(int i=0; i < connections.length; i++) { + pw.printf(" connections[%d]=%s\n", i, connections[i]); + } + pw.println(" voiceCallEndedRegistrants=" + voiceCallEndedRegistrants); + pw.println(" voiceCallStartedRegistrants=" + voiceCallStartedRegistrants); + pw.println(" callWaitingRegistrants=" + callWaitingRegistrants); + pw.println("droppedDuringPoll: size=" + droppedDuringPoll.size()); + for(int i = 0; i < droppedDuringPoll.size(); i++) { + pw.printf( " droppedDuringPoll[%d]=%s\n", i, droppedDuringPoll.get(i)); + } + pw.println(" ringingCall=" + ringingCall); + pw.println(" foregroundCall=" + foregroundCall); + pw.println(" backgroundCall=" + backgroundCall); + pw.println(" pendingMO=" + pendingMO); + pw.println(" hangupPendingMO=" + hangupPendingMO); + pw.println(" pendingCallInEcm=" + pendingCallInEcm); + pw.println(" mIsInEmergencyCall=" + mIsInEmergencyCall); + pw.println(" phone=" + phone); + pw.println(" desiredMute=" + desiredMute); + pw.println(" pendingCallClirMode=" + pendingCallClirMode); + pw.println(" state=" + state); + pw.println(" mIsEcmTimerCanceled=" + mIsEcmTimerCanceled); + } } diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnection.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnection.java index 64d018e2edba..4ef05eaaf274 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnection.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnection.java @@ -25,6 +25,9 @@ import com.android.internal.telephony.Phone; import com.android.internal.telephony.RILConstants; import com.android.internal.telephony.RetryManager; +import java.io.FileDescriptor; +import java.io.PrintWriter; + /** * {@hide} */ @@ -114,4 +117,10 @@ public class CdmaDataConnection extends DataConnection { protected void log(String s) { Log.d(LOG_TAG, "[" + getName() + "] " + s); } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("CdmaDataConnection extends:"); + super.dump(fd, pw, args); + } } diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java index 5f1a01458a74..7e5e7075e7be 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java @@ -46,6 +46,8 @@ import com.android.internal.telephony.Phone; import com.android.internal.util.AsyncChannel; import com.android.internal.telephony.RILConstants; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; /** @@ -1017,4 +1019,18 @@ public final class CdmaDataConnectionTracker extends DataConnectionTracker { protected void loge(String s) { Log.e(LOG_TAG, "[CdmaDCT] " + s); } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("CdmaDataConnectionTracker extends:"); + super.dump(fd, pw, args); + pw.println(" mCdmaPhone=" + mCdmaPhone); + pw.println(" mCdmaSSM=" + mCdmaSSM); + pw.println(" mPendingDataConnection=" + mPendingDataConnection); + pw.println(" mPendingRestartRadio=" + mPendingRestartRadio); + pw.println(" mSupportedApnTypes=" + mSupportedApnTypes); + pw.println(" mDefaultApnTypes=" + mDefaultApnTypes); + pw.println(" mDunApnTypes=" + mDunApnTypes); + pw.println(" mDefaultApnId=" + mDefaultApnId); + } } diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java index 09008cdae9e2..98a106a00d2e 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java @@ -36,6 +36,9 @@ import android.util.EventLog; import com.android.internal.telephony.gsm.GsmDataConnectionTracker; +import java.io.FileDescriptor; +import java.io.PrintWriter; + public class CdmaLteServiceStateTracker extends CdmaServiceStateTracker { CDMALTEPhone mCdmaLtePhone; @@ -519,4 +522,12 @@ public class CdmaLteServiceStateTracker extends CdmaServiceStateTracker { protected void loge(String s) { Log.e(LOG_TAG, "[CdmaLteSST] " + s); } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("CdmaLteServiceStateTracker extends:"); + super.dump(fd, pw, args); + pw.println(" mCdmaLtePhone=" + mCdmaLtePhone); + pw.println(" mLteSS=" + mLteSS); + } } diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java index 9ec56fcc936e..9f27696fb208 100755 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java @@ -54,6 +54,8 @@ import android.util.EventLog; import android.util.Log; import android.util.TimeUtils; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.Arrays; import java.util.Calendar; import java.util.Date; @@ -1665,4 +1667,43 @@ public class CdmaServiceStateTracker extends ServiceStateTracker { protected void loge(String s) { Log.e(LOG_TAG, "[CdmaSST] " + s); } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("CdmaServiceStateTracker extends:"); + super.dump(fd, pw, args); + pw.println(" phone=" + phone); + pw.println(" cellLoc=" + cellLoc); + pw.println(" newCellLoc=" + newCellLoc); + pw.println(" mCurrentOtaspMode=" + mCurrentOtaspMode); + pw.println(" mCdmaRoaming=" + mCdmaRoaming); + pw.println(" mRoamingIndicator=" + mRoamingIndicator); + pw.println(" mIsInPrl=" + mIsInPrl); + pw.println(" mDefaultRoamingIndicator=" + mDefaultRoamingIndicator); + pw.println(" mDataConnectionState=" + mDataConnectionState); + pw.println(" mNewDataConnectionState=" + mNewDataConnectionState); + pw.println(" mRegistrationState=" + mRegistrationState); + pw.println(" mNeedFixZone=" + mNeedFixZone); + pw.println(" mZoneOffset=" + mZoneOffset); + pw.println(" mZoneDst=" + mZoneDst); + pw.println(" mZoneTime=" + mZoneTime); + pw.println(" mGotCountryCode=" + mGotCountryCode); + pw.println(" mSavedTimeZone=" + mSavedTimeZone); + pw.println(" mSavedTime=" + mSavedTime); + pw.println(" mSavedAtTime=" + mSavedAtTime); + pw.println(" mNeedToRegForRuimLoaded=" + mNeedToRegForRuimLoaded); + pw.println(" mWakeLock=" + mWakeLock); + pw.println(" mCurPlmn=" + mCurPlmn); + pw.println(" mMdn=" + mMdn); + pw.println(" mHomeSystemId=" + mHomeSystemId); + pw.println(" mHomeNetworkId=" + mHomeNetworkId); + pw.println(" mMin=" + mMin); + pw.println(" mPrlVersion=" + mPrlVersion); + pw.println(" mIsMinInfoReady=" + mIsMinInfoReady); + pw.println(" isEriTextLoaded=" + isEriTextLoaded); + pw.println(" isSubscriptionFromRuim=" + isSubscriptionFromRuim); + pw.println(" mCdmaSSM=" + mCdmaSSM); + pw.println(" mRegistrationDeniedReason=" + mRegistrationDeniedReason); + pw.println(" currentCarrier=" + currentCarrier); + } } diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java index be13c35f552e..6e9cd51de500 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java +++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java @@ -74,7 +74,9 @@ import com.android.internal.telephony.uicc.UiccController; import com.android.internal.telephony.IccVmNotSupportedException; import com.android.internal.telephony.ServiceStateTracker; +import java.io.FileDescriptor; import java.io.IOException; +import java.io.PrintWriter; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; @@ -90,6 +92,7 @@ public class GSMPhone extends PhoneBase { // log. (Use "adb logcat -b radio" to see them.) static final String LOG_TAG = "GSM"; private static final boolean LOCAL_DEBUG = true; + private static final boolean VDBG = false; /* STOP SHIP if true */ // Key used to read/write current ciphering state public static final String CIPHERING_KEY = "ciphering_key"; @@ -1487,4 +1490,18 @@ public class GSMPhone extends PhoneBase { mIccRecords.unregisterForRecordsLoaded(this); } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("GSMPhone extends:"); + super.dump(fd, pw, args); + pw.println(" mCT=" + mCT); + pw.println(" mSST=" + mSST); + pw.println(" mPendingMMIs=" + mPendingMMIs); + pw.println(" mSimPhoneBookIntManager=" + mSimPhoneBookIntManager); + pw.println(" mSimSmsIntManager=" + mSimSmsIntManager); + pw.println(" mSubInfo=" + mSubInfo); + if (VDBG) pw.println(" mImei=" + mImei); + if (VDBG) pw.println(" mImeiSv=" + mImeiSv); + pw.println(" mVmNumber=" + mVmNumber); + } } diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmCallTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmCallTracker.java index b4e0775b5189..e86d85388b06 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmCallTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmCallTracker.java @@ -43,6 +43,8 @@ import com.android.internal.telephony.gsm.GSMPhone; import com.android.internal.telephony.gsm.GsmCall; import com.android.internal.telephony.gsm.GsmConnection; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.List; import java.util.ArrayList; @@ -922,4 +924,28 @@ public final class GsmCallTracker extends CallTracker { protected void log(String msg) { Log.d(LOG_TAG, "[GsmCallTracker] " + msg); } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("GsmCallTracker extends:"); + super.dump(fd, pw, args); + pw.println("connections: length=" + connections.length); + for(int i=0; i < connections.length; i++) { + pw.printf(" connections[%d]=%s\n", i, connections[i]); + } + pw.println(" voiceCallEndedRegistrants=" + voiceCallEndedRegistrants); + pw.println(" voiceCallStartedRegistrants=" + voiceCallStartedRegistrants); + pw.println(" droppedDuringPoll: size=" + droppedDuringPoll.size()); + for(int i = 0; i < droppedDuringPoll.size(); i++) { + pw.printf( " droppedDuringPoll[%d]=%s\n", i, droppedDuringPoll.get(i)); + } + pw.println(" ringingCall=" + ringingCall); + pw.println(" foregroundCall=" + foregroundCall); + pw.println(" backgroundCall=" + backgroundCall); + pw.println(" pendingMO=" + pendingMO); + pw.println(" hangupPendingMO=" + hangupPendingMO); + pw.println(" phone=" + phone); + pw.println(" desiredMute=" + desiredMute); + pw.println(" state=" + state); + } } diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java index 4956ef41ff8f..fec01584dc63 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnection.java @@ -28,6 +28,9 @@ import com.android.internal.telephony.PhoneBase; import com.android.internal.telephony.RILConstants; import com.android.internal.telephony.RetryManager; +import java.io.FileDescriptor; +import java.io.PrintWriter; + /** * {@hide} */ @@ -153,4 +156,11 @@ public class GsmDataConnection extends DataConnection { return Patterns.IP_ADDRESS.matcher(address).matches(); } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("GsmDataConnection extends:"); + super.dump(fd, pw, args); + pw.println(" mProfileId=" + mProfileId); + } } diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java index d1873ebbf2c9..40ee58c078f7 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java @@ -65,6 +65,8 @@ import com.android.internal.telephony.RILConstants; import com.android.internal.telephony.RetryManager; import com.android.internal.util.AsyncChannel; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -2562,4 +2564,16 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { protected void loge(String s) { Log.e(LOG_TAG, "[GsmDCT] " + s); } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("GsmDataConnectionTracker extends:"); + super.dump(fd, pw, args); + pw.println(" RADIO_TESTS=" + RADIO_TESTS); + pw.println(" mReregisterOnReconnectFailure=" + mReregisterOnReconnectFailure); + pw.println(" mResolver=" + mResolver); + pw.println(" canSetPreferApn=" + canSetPreferApn); + pw.println(" mApnObserver=" + mApnObserver); + pw.println(" getOverallState=" + getOverallState()); + } } diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java index 080d90cd34f7..662f1f6b0ac0 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java @@ -59,6 +59,8 @@ import android.util.EventLog; import android.util.Log; import android.util.TimeUtils; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -1681,4 +1683,40 @@ final class GsmServiceStateTracker extends ServiceStateTracker { private static void sloge(String s) { Log.e(LOG_TAG, "[GsmSST] " + s); } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("GsmServiceStateTracker extends:"); + super.dump(fd, pw, args); + pw.println(" phone=" + phone); + pw.println(" cellLoc=" + cellLoc); + pw.println(" newCellLoc=" + newCellLoc); + pw.println(" mPreferredNetworkType=" + mPreferredNetworkType); + pw.println(" gprsState=" + gprsState); + pw.println(" newGPRSState=" + newGPRSState); + pw.println(" mMaxDataCalls=" + mMaxDataCalls); + pw.println(" mNewMaxDataCalls=" + mNewMaxDataCalls); + pw.println(" mReasonDataDenied=" + mReasonDataDenied); + pw.println(" mNewReasonDataDenied=" + mNewReasonDataDenied); + pw.println(" mGsmRoaming=" + mGsmRoaming); + pw.println(" mDataRoaming=" + mDataRoaming); + pw.println(" mEmergencyOnly=" + mEmergencyOnly); + pw.println(" mNeedFixZone=" + mNeedFixZone); + pw.println(" mZoneOffset=" + mZoneOffset); + pw.println(" mZoneDst=" + mZoneDst); + pw.println(" mZoneTime=" + mZoneTime); + pw.println(" mGotCountryCode=" + mGotCountryCode); + pw.println(" mNitzUpdatedTime=" + mNitzUpdatedTime); + pw.println(" mSavedTimeZone=" + mSavedTimeZone); + pw.println(" mSavedTime=" + mSavedTime); + pw.println(" mSavedAtTime=" + mSavedAtTime); + pw.println(" mNeedToRegForSimLoaded=" + mNeedToRegForSimLoaded); + pw.println(" mStartedGprsRegCheck=" + mStartedGprsRegCheck); + pw.println(" mReportedGprsNoReg=" + mReportedGprsNoReg); + pw.println(" mNotification=" + mNotification); + pw.println(" mWakeLock=" + mWakeLock); + pw.println(" curSpn=" + curSpn); + pw.println(" curPlmn=" + curPlmn); + pw.println(" curSpnRule=" + curSpnRule); + } } diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index b310d93d97de..f4c0841d694a 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -638,7 +638,6 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> - <activity android:name="StackActivity" @@ -649,5 +648,14 @@ </intent-filter> </activity> + <activity + android:name="TransformsAndAnimationsActivity" + android:label="_TransformAnim"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> </manifest> diff --git a/tests/HwAccelerationTest/res/layout/transforms_and_animations.xml b/tests/HwAccelerationTest/res/layout/transforms_and_animations.xml new file mode 100644 index 000000000000..1595502f6db9 --- /dev/null +++ b/tests/HwAccelerationTest/res/layout/transforms_and_animations.xml @@ -0,0 +1,145 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <CheckBox android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="None" + android:checked="true" + android:id="@+id/layersNoneCB"/> + + <CheckBox android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Hardware" + android:id="@+id/layersHwCB"/> + + <CheckBox android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Software" + android:id="@+id/layersSwCB"/> + + </LinearLayout> + + <LinearLayout android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <Button android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="1" + android:id="@+id/button1"/> + + <Button android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="2" + android:id="@+id/button2"/> + + <Button android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="3" + android:id="@+id/button3"/> + + </LinearLayout> + + <LinearLayout android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <Button android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="1a" + android:id="@+id/button1a"/> + + <Button android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="2a" + android:id="@+id/button2a"/> + + <Button android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="3a" + android:id="@+id/button3a"/> + + </LinearLayout> + + <LinearLayout android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <Button android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="1b" + android:id="@+id/button1b"/> + + <Button android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="2b" + android:id="@+id/button2b"/> + + <Button android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="3b" + android:id="@+id/button3b"/> + + </LinearLayout> + + <view class="com.android.test.hwui.TransformsAndAnimationsActivity$MyLayout" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <Button android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="4" + android:id="@+id/button4"/> + + <Button android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="5" + android:id="@+id/button5"/> + + <Button android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="6" + android:id="@+id/button6"/> + + </view> + + <LinearLayout android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <Button android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="7" + android:id="@+id/button7"/> + + <Button android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="8" + android:id="@+id/button8"/> + + </LinearLayout> + +</LinearLayout> diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TransformsAndAnimationsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TransformsAndAnimationsActivity.java new file mode 100644 index 000000000000..684d17944549 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TransformsAndAnimationsActivity.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.test.hwui; + +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.Transformation; +import android.view.animation.TranslateAnimation; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.LinearLayout; + +public class TransformsAndAnimationsActivity extends Activity { + Button button1; + Button button2; + Button button3; + Button button1a; + Button button2a; + Button button3a; + Button button1b; + Button button2b; + Button button3b; + Button button4; + Button button5; + Button button6; + Button button7; + Button button8; + CheckBox layersNoneCB; + CheckBox layersHardwareCB; + CheckBox layersSoftwareCB; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.transforms_and_animations); + + button1 = (Button) findViewById(R.id.button1); + button2 = (Button) findViewById(R.id.button2); + button3 = (Button) findViewById(R.id.button3); + button1a = (Button) findViewById(R.id.button1a); + button2a = (Button) findViewById(R.id.button2a); + button3a = (Button) findViewById(R.id.button3a); + button1b = (Button) findViewById(R.id.button1b); + button2b = (Button) findViewById(R.id.button2b); + button3b = (Button) findViewById(R.id.button3b); + button4 = (Button) findViewById(R.id.button4); + button5 = (Button) findViewById(R.id.button5); + button6 = (Button) findViewById(R.id.button6); + button7 = (Button) findViewById(R.id.button7); + button8 = (Button) findViewById(R.id.button8); + layersNoneCB = (CheckBox) findViewById(R.id.layersNoneCB); + layersHardwareCB = (CheckBox) findViewById(R.id.layersHwCB); + layersSoftwareCB = (CheckBox) findViewById(R.id.layersSwCB); + + layersNoneCB.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + setLayerType(View.LAYER_TYPE_NONE); + layersHardwareCB.setChecked(false); + layersSoftwareCB.setChecked(false); + } + } + }); + + layersSoftwareCB.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + setLayerType(View.LAYER_TYPE_SOFTWARE); + layersHardwareCB.setChecked(false); + layersNoneCB.setChecked(false); + } + } + }); + + layersHardwareCB.setOnCheckedChangeListener(new CheckBox.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + setLayerType(View.LAYER_TYPE_HARDWARE); + layersNoneCB.setChecked(false); + layersSoftwareCB.setChecked(false); + } + } + }); + + button1a.setAlpha(.5f); + button2a.setAlpha(.5f); + button3a.setAlpha(.5f); + button3.setTranslationX(50); + button7.setTranslationX(50); + button8.setTranslationX(50); + + final AlphaAnimation alphaAnim = new AlphaAnimation(1, 0); + alphaAnim.setDuration(1000); + alphaAnim.setRepeatCount(Animation.INFINITE); + alphaAnim.setRepeatMode(Animation.REVERSE); + + final TranslateAnimation transAnim = new TranslateAnimation(0, -50, 0, 0); + transAnim.setDuration(1000); + transAnim.setRepeatCount(Animation.INFINITE); + transAnim.setRepeatMode(Animation.REVERSE); + + getWindow().getDecorView().postDelayed(new Runnable() { + @Override + public void run() { + button1.startAnimation(alphaAnim); + button2.startAnimation(alphaAnim); + button3.startAnimation(alphaAnim); + + button1a.startAnimation(alphaAnim); + button2a.startAnimation(alphaAnim); + button3a.startAnimation(alphaAnim); + + button1b.startAnimation(alphaAnim); + button2b.startAnimation(alphaAnim); + button3b.startAnimation(alphaAnim); + startAnimator(button1b); + startAnimator(button2b); + startAnimator(button3b); + + button7.startAnimation(transAnim); + button8.startAnimation(transAnim); + } + }, 2000); + } + + private void setLayerType(int layerType) { + button1.setLayerType(layerType, null); + button2.setLayerType(layerType, null); + button3.setLayerType(layerType, null); + button1a.setLayerType(layerType, null); + button2a.setLayerType(layerType, null); + button3a.setLayerType(layerType, null); + button1b.setLayerType(layerType, null); + button2b.setLayerType(layerType, null); + button3b.setLayerType(layerType, null); + button4.setLayerType(layerType, null); + button5.setLayerType(layerType, null); + button6.setLayerType(layerType, null); + button7.setLayerType(layerType, null); + button8.setLayerType(layerType, null); + } + + private void startAnimator(View target) { + ObjectAnimator anim1b = ObjectAnimator.ofFloat(target, View.ALPHA, 0); + anim1b.setRepeatCount(ValueAnimator.INFINITE); + anim1b.setRepeatMode(ValueAnimator.REVERSE); + anim1b.setDuration(1000); + anim1b.start(); + } + + public static class MyLayout extends LinearLayout { + + public MyLayout(Context context) { + super(context); + setStaticTransformationsEnabled(true); + } + + public MyLayout(Context context, AttributeSet attrs) { + super(context, attrs); + setStaticTransformationsEnabled(true); + } + + public MyLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + setStaticTransformationsEnabled(true); + } + + @Override + protected boolean getChildStaticTransformation(View child, Transformation t) { + t.clear(); + t.setAlpha(.35f); + + return true; + } + } +} + diff --git a/tests/RenderScriptTests/SurfaceTexture/Android.mk b/tests/RenderScriptTests/SurfaceTexture/Android.mk new file mode 100644 index 000000000000..bbd4d55b13aa --- /dev/null +++ b/tests/RenderScriptTests/SurfaceTexture/Android.mk @@ -0,0 +1,29 @@ +# +# Copyright (C) 2012 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src) + +# TODO: build fails with this set +# LOCAL_SDK_VERSION := current + +LOCAL_PACKAGE_NAME := RsSurfaceTextureOpaque + +include $(BUILD_PACKAGE) diff --git a/tests/RenderScriptTests/SurfaceTexture/AndroidManifest.xml b/tests/RenderScriptTests/SurfaceTexture/AndroidManifest.xml new file mode 100644 index 000000000000..8aaa2394336b --- /dev/null +++ b/tests/RenderScriptTests/SurfaceTexture/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.android.rs.sto"> + <uses-sdk android:minSdkVersion="14" /> + <uses-permission android:name="android.permission.CAMERA" /> + <uses-feature android:name="android.hardware.camera" /> + <uses-feature android:name="android.hardware.camera.autofocus" /> + + <application + android:label="RsSurfaceTextureOpaque" + android:hardwareAccelerated="true" + android:icon="@drawable/test_pattern"> + + <activity android:name="SurfaceTextureOpaque"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/tests/RenderScriptTests/SurfaceTexture/res/drawable/test_pattern.png b/tests/RenderScriptTests/SurfaceTexture/res/drawable/test_pattern.png Binary files differnew file mode 100644 index 000000000000..e7d145554c00 --- /dev/null +++ b/tests/RenderScriptTests/SurfaceTexture/res/drawable/test_pattern.png diff --git a/tests/RenderScriptTests/SurfaceTexture/src/com/example/android/rs/sto/CameraCapture.java b/tests/RenderScriptTests/SurfaceTexture/src/com/example/android/rs/sto/CameraCapture.java new file mode 100644 index 000000000000..afdab41b684c --- /dev/null +++ b/tests/RenderScriptTests/SurfaceTexture/src/com/example/android/rs/sto/CameraCapture.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.example.android.rs.sto; + +import android.graphics.SurfaceTexture; +import android.hardware.Camera; +import android.os.SystemClock; +import android.util.Log; + +import java.io.IOException; +import java.util.List; + +public class CameraCapture { + + public interface CameraFrameListener { + public void onNewCameraFrame(); + } + + static final int FRAMES_PER_SEC = 30; + + private Camera mCamera; + private SurfaceTexture mSurfaceTexture; + + private int mProgram; + + private int mCameraTransformHandle; + private int mTexSamplerHandle; + private int mTexCoordHandle; + private int mPosCoordHandle; + + private float[] mCameraTransform = new float[16]; + + private int mCameraId = 0; + private int mWidth; + private int mHeight; + + private long mStartCaptureTime = 0; + + private boolean mNewFrameAvailable = false; + private boolean mIsOpen = false; + + private CameraFrameListener mListener; + + public synchronized void beginCapture(int cameraId, int width, int height, + SurfaceTexture st) { + mCameraId = cameraId; + mSurfaceTexture = st; + + // Open the camera + openCamera(width, height); + + // Start the camera + mStartCaptureTime = SystemClock.elapsedRealtime(); + mCamera.startPreview(); + mIsOpen = true; + } + + public void getCurrentFrame() { + if (checkNewFrame()) { + if (mStartCaptureTime > 0 && SystemClock.elapsedRealtime() - mStartCaptureTime > 2000) { + // Lock white-balance and exposure for effects + Log.i("CC", "Locking white-balance and exposure!"); + Camera.Parameters params = mCamera.getParameters(); + params.setAutoWhiteBalanceLock(true); + params.setAutoExposureLock(true); + //mCamera.setParameters(params); + mStartCaptureTime = 0; + } + + mSurfaceTexture.updateTexImage(); + mSurfaceTexture.getTransformMatrix(mCameraTransform); + + // display it here + } + } + + public synchronized boolean hasNewFrame() { + return mNewFrameAvailable; + } + + public synchronized void endCapture() { + mIsOpen = false; + if (mCamera != null) { + mCamera.release(); + mCamera = null; + mSurfaceTexture = null; + } + } + + public synchronized boolean isOpen() { + return mIsOpen; + } + + public int getWidth() { + return mWidth; + } + + public int getHeight() { + return mHeight; + } + + public void setCameraFrameListener(CameraFrameListener listener) { + mListener = listener; + } + + private void openCamera(int width, int height) { + // Setup camera + mCamera = Camera.open(mCameraId); + mCamera.setParameters(calcCameraParameters(width, height)); + + // Create camera surface texture + try { + mCamera.setPreviewTexture(mSurfaceTexture); + } catch (IOException e) { + throw new RuntimeException("Could not bind camera surface texture: " + + e.getMessage() + "!"); + } + + // Connect SurfaceTexture to callback + mSurfaceTexture.setOnFrameAvailableListener(onCameraFrameAvailableListener); + } + + private Camera.Parameters calcCameraParameters(int width, int height) { + Camera.Parameters params = mCamera.getParameters(); + params.setPreviewSize(mWidth, mHeight); + + // Find closest size + int closestSize[] = findClosestSize(width, height, params); + mWidth = closestSize[0]; + mHeight = closestSize[1]; + params.setPreviewSize(mWidth, mHeight); + + // Find closest FPS + int closestRange[] = findClosestFpsRange(FRAMES_PER_SEC, params); + + params.setPreviewFpsRange(closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX], + closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]); + + return params; + } + + private int[] findClosestSize(int width, int height, Camera.Parameters parameters) { + List<Camera.Size> previewSizes = parameters.getSupportedPreviewSizes(); + int closestWidth = -1; + int closestHeight = -1; + int smallestWidth = previewSizes.get(0).width; + int smallestHeight = previewSizes.get(0).height; + for (Camera.Size size : previewSizes) { + // Best match defined as not being larger in either dimension than + // the requested size, but as close as possible. The below isn't a + // stable selection (reording the size list can give different + // results), but since this is a fallback nicety, that's acceptable. + if ( size.width <= width && + size.height <= height && + size.width >= closestWidth && + size.height >= closestHeight) { + closestWidth = size.width; + closestHeight = size.height; + } + if ( size.width < smallestWidth && + size.height < smallestHeight) { + smallestWidth = size.width; + smallestHeight = size.height; + } + } + if (closestWidth == -1) { + // Requested size is smaller than any listed size; match with smallest possible + closestWidth = smallestWidth; + closestHeight = smallestHeight; + } + int[] closestSize = {closestWidth, closestHeight}; + return closestSize; + } + + private int[] findClosestFpsRange(int fps, Camera.Parameters params) { + List<int[]> supportedFpsRanges = params.getSupportedPreviewFpsRange(); + int[] closestRange = supportedFpsRanges.get(0); + int fpsk = fps * 1000; + int minDiff = 1000000; + for (int[] range : supportedFpsRanges) { + int low = range[Camera.Parameters.PREVIEW_FPS_MIN_INDEX]; + int high = range[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]; + if (low <= fpsk && high >= fpsk) { + int diff = (fpsk - low) + (high - fpsk); + if (diff < minDiff) { + closestRange = range; + minDiff = diff; + } + } + } + Log.i("CC", "Found closest range: " + + closestRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX] + " - " + + closestRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]); + return closestRange; + } + + private synchronized void signalNewFrame() { + mNewFrameAvailable = true; + if (mListener != null) { + mListener.onNewCameraFrame(); + } + } + + private synchronized boolean checkNewFrame() { + if (mNewFrameAvailable) { + mNewFrameAvailable = false; + return true; + } + return false; + } + + private SurfaceTexture.OnFrameAvailableListener onCameraFrameAvailableListener = + new SurfaceTexture.OnFrameAvailableListener() { + @Override + public void onFrameAvailable(SurfaceTexture surfaceTexture) { + signalNewFrame(); + } + }; +} diff --git a/tests/RenderScriptTests/SurfaceTexture/src/com/example/android/rs/sto/SurfaceTextureOpaque.java b/tests/RenderScriptTests/SurfaceTexture/src/com/example/android/rs/sto/SurfaceTextureOpaque.java new file mode 100644 index 000000000000..a51edaaade7d --- /dev/null +++ b/tests/RenderScriptTests/SurfaceTexture/src/com/example/android/rs/sto/SurfaceTextureOpaque.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.rs.sto; + +import android.app.Activity; +import android.os.Bundle; +import android.provider.Settings.System; +import android.util.Log; +import android.view.View; +import android.graphics.SurfaceTexture; + +import java.lang.Runtime; + +public class SurfaceTextureOpaque extends Activity { + private SurfaceTextureOpaqueView mView; + CameraCapture mCC; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mView = new SurfaceTextureOpaqueView(this); + setContentView(mView); + } + + @Override + protected void onResume() { + super.onResume(); + mView.resume(); + startCamera(); + } + + @Override + protected void onPause() { + super.onPause(); + mView.pause(); + mCC.endCapture(); + } + + cfl mCFL; + public void startCamera() { + mCC = new CameraCapture(); + mCFL = new cfl(); + + mCC.setCameraFrameListener(mCFL); + + mCC.beginCapture(1, 640, 480, mView.getST()); + } + + public class cfl implements CameraCapture.CameraFrameListener { + public void onNewCameraFrame() { + mView.mRender.newFrame(); + } + } + +} + diff --git a/tests/RenderScriptTests/SurfaceTexture/src/com/example/android/rs/sto/SurfaceTextureOpaqueRS.java b/tests/RenderScriptTests/SurfaceTexture/src/com/example/android/rs/sto/SurfaceTextureOpaqueRS.java new file mode 100644 index 000000000000..b638b7d16e1f --- /dev/null +++ b/tests/RenderScriptTests/SurfaceTexture/src/com/example/android/rs/sto/SurfaceTextureOpaqueRS.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.rs.sto; + +import android.content.res.Resources; +import android.renderscript.*; +import android.graphics.SurfaceTexture; +import android.util.Log; + + +public class SurfaceTextureOpaqueRS { + static final private int NUM_CAMERA_PREVIEW_BUFFERS = 2; + + public SurfaceTextureOpaqueRS() { + } + + private Resources mRes; + private RenderScriptGL mRS; + private ScriptC_sto mScript; + private SurfaceTexture mST; + private Allocation mSto; + private Allocation mSto2; + private Allocation mRto; + private ProgramFragment mPF; + + public void init(RenderScriptGL rs, Resources res) { + mRS = rs; + mRes = res; + + Type.Builder tb = new Type.Builder(mRS, Element.RGBA_8888(mRS)); + tb.setX(640); + tb.setY(480); + mSto = Allocation.createTyped(mRS, tb.create(), Allocation.USAGE_GRAPHICS_TEXTURE | + Allocation.USAGE_IO_INPUT); + mRto = Allocation.createTyped(mRS, tb.create(), Allocation.USAGE_GRAPHICS_RENDER_TARGET | + Allocation.USAGE_IO_OUTPUT); + mSto2 = Allocation.createTyped(mRS, tb.create(), Allocation.USAGE_GRAPHICS_TEXTURE | + Allocation.USAGE_IO_INPUT); + mST = mSto.getSurfaceTexture(); + mRto.setSurfaceTexture(mSto2.getSurfaceTexture()); + + ProgramFragmentFixedFunction.Builder pfb = new ProgramFragmentFixedFunction.Builder(rs); + pfb.setTexture(ProgramFragmentFixedFunction.Builder.EnvMode.REPLACE, + ProgramFragmentFixedFunction.Builder.Format.RGBA, 0); + mPF = pfb.create(); + mPF.bindSampler(Sampler.CLAMP_NEAREST(mRS), 0); + rs.bindProgramFragment(mPF); + + mScript = new ScriptC_sto(mRS, mRes, R.raw.sto); + mScript.set_sto(mSto); + mScript.set_rto(mRto); + mScript.set_sto2(mSto2); + mScript.set_pf(mPF); + + mRS.bindRootScript(mScript); + + + android.util.Log.v("sto", "Init complete"); + } + + SurfaceTexture getST() { + return mST; + } + + public void newFrame() { + mSto.ioReceive(); + } + +} diff --git a/tests/RenderScriptTests/SurfaceTexture/src/com/example/android/rs/sto/SurfaceTextureOpaqueView.java b/tests/RenderScriptTests/SurfaceTexture/src/com/example/android/rs/sto/SurfaceTextureOpaqueView.java new file mode 100644 index 000000000000..f5e49f2cefc9 --- /dev/null +++ b/tests/RenderScriptTests/SurfaceTexture/src/com/example/android/rs/sto/SurfaceTextureOpaqueView.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.android.rs.sto; + + +import android.renderscript.RSSurfaceView; +import android.renderscript.RenderScriptGL; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.SurfaceTexture; +import android.util.Log; + +public class SurfaceTextureOpaqueView extends RSSurfaceView { + + public SurfaceTextureOpaqueView(Context context) { + super(context); + } + + RenderScriptGL mRS; + SurfaceTextureOpaqueRS mRender; + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mRS != null) { + mRS = null; + destroyRenderScriptGL(); + } + } + + SurfaceTexture getST() { + RenderScriptGL.SurfaceConfig sc = new RenderScriptGL.SurfaceConfig(); + mRS = createRenderScriptGL(sc); + mRender = new SurfaceTextureOpaqueRS(); + mRender.init(mRS, getResources()); + return mRender.getST(); + } + +} + + diff --git a/tests/RenderScriptTests/SurfaceTexture/src/com/example/android/rs/sto/sto.rs b/tests/RenderScriptTests/SurfaceTexture/src/com/example/android/rs/sto/sto.rs new file mode 100644 index 000000000000..efa901ae93fd --- /dev/null +++ b/tests/RenderScriptTests/SurfaceTexture/src/com/example/android/rs/sto/sto.rs @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma version(1) +#pragma rs java_package_name(com.example.android.rs.sto) + +#pragma stateFragment(parent) + +#include "rs_graphics.rsh" + + +rs_program_fragment pf; +rs_allocation sto; // camera in +rs_allocation sto2; +rs_allocation rto; // render target + +int root() { + rsgBindTexture(pf, 0, sto); + +#if 1 + rsgBindColorTarget(rto, 0); + + rsgClearColor(0.f, 1.f, 0.f, 1.f); + rsgDrawQuadTexCoords(0, 0, 0, 0,0, + 0,500,0, 1,0, + 500,500,0, 1, 1, + 500, 0, 0, 0, 1 ); + rsgClearColorTarget(0); + + // io ops + rsAllocationIoSend(rto); + rsAllocationIoReceive(sto2); + + rsgBindTexture(pf, 0, sto2); +#endif + + rsgClearColor(0.f, 1.f, 0.f, 1.f); + rsgDrawQuadTexCoords(0, 0, 0, 0,0, + 0,500,0, 1,0, + 500,500,0, 1, 1, + 500, 0, 0, 0, 1 ); + + return 1; +} + diff --git a/voip/java/android/net/rtp/AudioGroup.java b/voip/java/android/net/rtp/AudioGroup.java index 3e7ace83c99b..8c190621934f 100644 --- a/voip/java/android/net/rtp/AudioGroup.java +++ b/voip/java/android/net/rtp/AudioGroup.java @@ -142,34 +142,34 @@ public class AudioGroup { private native void nativeSetMode(int mode); // Package-private method used by AudioStream.join(). - synchronized void add(AudioStream stream, AudioCodec codec, int dtmfType) { + synchronized void add(AudioStream stream) { if (!mStreams.containsKey(stream)) { try { - int socket = stream.dup(); + AudioCodec codec = stream.getCodec(); String codecSpec = String.format("%d %s %s", codec.type, codec.rtpmap, codec.fmtp); - nativeAdd(stream.getMode(), socket, + int id = nativeAdd(stream.getMode(), stream.getSocket(), stream.getRemoteAddress().getHostAddress(), - stream.getRemotePort(), codecSpec, dtmfType); - mStreams.put(stream, socket); + stream.getRemotePort(), codecSpec, stream.getDtmfType()); + mStreams.put(stream, id); } catch (NullPointerException e) { throw new IllegalStateException(e); } } } - private native void nativeAdd(int mode, int socket, String remoteAddress, + private native int nativeAdd(int mode, int socket, String remoteAddress, int remotePort, String codecSpec, int dtmfType); // Package-private method used by AudioStream.join(). synchronized void remove(AudioStream stream) { - Integer socket = mStreams.remove(stream); - if (socket != null) { - nativeRemove(socket); + Integer id = mStreams.remove(stream); + if (id != null) { + nativeRemove(id); } } - private native void nativeRemove(int socket); + private native void nativeRemove(int id); /** * Sends a DTMF digit to every {@link AudioStream} in this group. Currently @@ -192,15 +192,14 @@ public class AudioGroup { * Removes every {@link AudioStream} in this group. */ public void clear() { - synchronized (this) { - mStreams.clear(); - nativeRemove(-1); + for (AudioStream stream : getStreams()) { + stream.join(null); } } @Override protected void finalize() throws Throwable { - clear(); + nativeRemove(0); super.finalize(); } } diff --git a/voip/java/android/net/rtp/AudioStream.java b/voip/java/android/net/rtp/AudioStream.java index b7874f753fde..5cd1abcb0376 100644 --- a/voip/java/android/net/rtp/AudioStream.java +++ b/voip/java/android/net/rtp/AudioStream.java @@ -94,7 +94,7 @@ public class AudioStream extends RtpStream { mGroup = null; } if (group != null) { - group.add(this, mCodec, mDtmfType); + group.add(this); mGroup = group; } } diff --git a/voip/java/android/net/rtp/RtpStream.java b/voip/java/android/net/rtp/RtpStream.java index e94ac426430b..b9d75cd07824 100644 --- a/voip/java/android/net/rtp/RtpStream.java +++ b/voip/java/android/net/rtp/RtpStream.java @@ -54,7 +54,7 @@ public class RtpStream { private int mRemotePort = -1; private int mMode = MODE_NORMAL; - private int mNative; + private int mSocket = -1; static { System.loadLibrary("rtp_jni"); } @@ -165,7 +165,9 @@ public class RtpStream { mRemotePort = port; } - synchronized native int dup(); + int getSocket() { + return mSocket; + } /** * Releases allocated resources. The stream becomes inoperable after calling @@ -175,13 +177,15 @@ public class RtpStream { * @see #isBusy() */ public void release() { - if (isBusy()) { - throw new IllegalStateException("Busy"); + synchronized (this) { + if (isBusy()) { + throw new IllegalStateException("Busy"); + } + close(); } - close(); } - private synchronized native void close(); + private native void close(); @Override protected void finalize() throws Throwable { diff --git a/voip/jni/rtp/AudioGroup.cpp b/voip/jni/rtp/AudioGroup.cpp index b9bbd164ded7..673a6504300a 100644 --- a/voip/jni/rtp/AudioGroup.cpp +++ b/voip/jni/rtp/AudioGroup.cpp @@ -478,7 +478,7 @@ public: bool setMode(int mode); bool sendDtmf(int event); bool add(AudioStream *stream); - bool remove(int socket); + bool remove(AudioStream *stream); bool platformHasAec() { return mPlatformHasAec; } private: @@ -691,20 +691,19 @@ bool AudioGroup::add(AudioStream *stream) return true; } -bool AudioGroup::remove(int socket) +bool AudioGroup::remove(AudioStream *stream) { mNetworkThread->requestExitAndWait(); - for (AudioStream *stream = mChain; stream->mNext; stream = stream->mNext) { - AudioStream *target = stream->mNext; - if (target->mSocket == socket) { - if (epoll_ctl(mEventQueue, EPOLL_CTL_DEL, socket, NULL)) { + for (AudioStream *chain = mChain; chain->mNext; chain = chain->mNext) { + if (chain->mNext == stream) { + if (epoll_ctl(mEventQueue, EPOLL_CTL_DEL, stream->mSocket, NULL)) { ALOGE("epoll_ctl: %s", strerror(errno)); return false; } - stream->mNext = target->mNext; - ALOGD("stream[%d] leaves group[%d]", socket, mDeviceSocket); - delete target; + chain->mNext = stream->mNext; + ALOGD("stream[%d] leaves group[%d]", stream->mSocket, mDeviceSocket); + delete stream; break; } } @@ -931,7 +930,7 @@ exit: static jfieldID gNative; static jfieldID gMode; -void add(JNIEnv *env, jobject thiz, jint mode, +int add(JNIEnv *env, jobject thiz, jint mode, jint socket, jstring jRemoteAddress, jint remotePort, jstring jCodecSpec, jint dtmfType) { @@ -943,16 +942,22 @@ void add(JNIEnv *env, jobject thiz, jint mode, sockaddr_storage remote; if (parse(env, jRemoteAddress, remotePort, &remote) < 0) { // Exception already thrown. - return; + return 0; } if (!jCodecSpec) { jniThrowNullPointerException(env, "codecSpec"); - return; + return 0; } const char *codecSpec = env->GetStringUTFChars(jCodecSpec, NULL); if (!codecSpec) { // Exception already thrown. - return; + return 0; + } + socket = dup(socket); + if (socket == -1) { + jniThrowException(env, "java/lang/IllegalStateException", + "cannot get stream socket"); + return 0; } // Create audio codec. @@ -1001,7 +1006,7 @@ void add(JNIEnv *env, jobject thiz, jint mode, // Succeed. env->SetIntField(thiz, gNative, (int)group); - return; + return (int)stream; error: delete group; @@ -1009,13 +1014,14 @@ error: delete codec; close(socket); env->SetIntField(thiz, gNative, 0); + return 0; } -void remove(JNIEnv *env, jobject thiz, jint socket) +void remove(JNIEnv *env, jobject thiz, jint stream) { AudioGroup *group = (AudioGroup *)env->GetIntField(thiz, gNative); if (group) { - if (socket == -1 || !group->remove(socket)) { + if (!stream || !group->remove((AudioStream *)stream)) { delete group; env->SetIntField(thiz, gNative, 0); } @@ -1039,7 +1045,7 @@ void sendDtmf(JNIEnv *env, jobject thiz, jint event) } JNINativeMethod gMethods[] = { - {"nativeAdd", "(IILjava/lang/String;ILjava/lang/String;I)V", (void *)add}, + {"nativeAdd", "(IILjava/lang/String;ILjava/lang/String;I)I", (void *)add}, {"nativeRemove", "(I)V", (void *)remove}, {"nativeSetMode", "(I)V", (void *)setMode}, {"nativeSendDtmf", "(I)V", (void *)sendDtmf}, diff --git a/voip/jni/rtp/RtpStream.cpp b/voip/jni/rtp/RtpStream.cpp index 6540099e83f2..bfe8e24b4fd3 100644 --- a/voip/jni/rtp/RtpStream.cpp +++ b/voip/jni/rtp/RtpStream.cpp @@ -33,11 +33,11 @@ extern int parse(JNIEnv *env, jstring jAddress, int port, sockaddr_storage *ss); namespace { -jfieldID gNative; +jfieldID gSocket; jint create(JNIEnv *env, jobject thiz, jstring jAddress) { - env->SetIntField(thiz, gNative, -1); + env->SetIntField(thiz, gSocket, -1); sockaddr_storage ss; if (parse(env, jAddress, 0, &ss) < 0) { @@ -58,7 +58,7 @@ jint create(JNIEnv *env, jobject thiz, jstring jAddress) &((sockaddr_in *)&ss)->sin_port : &((sockaddr_in6 *)&ss)->sin6_port; uint16_t port = ntohs(*p); if ((port & 1) == 0) { - env->SetIntField(thiz, gNative, socket); + env->SetIntField(thiz, gSocket, socket); return port; } ::close(socket); @@ -75,7 +75,7 @@ jint create(JNIEnv *env, jobject thiz, jstring jAddress) *p = htons(port); if (bind(socket, (sockaddr *)&ss, sizeof(ss)) == 0) { - env->SetIntField(thiz, gNative, socket); + env->SetIntField(thiz, gSocket, socket); return port; } } @@ -86,25 +86,15 @@ jint create(JNIEnv *env, jobject thiz, jstring jAddress) return -1; } -jint dup(JNIEnv *env, jobject thiz) -{ - int socket = ::dup(env->GetIntField(thiz, gNative)); - if (socket == -1) { - jniThrowException(env, "java/lang/IllegalStateException", strerror(errno)); - } - return socket; -} - void close(JNIEnv *env, jobject thiz) { - int socket = env->GetIntField(thiz, gNative); + int socket = env->GetIntField(thiz, gSocket); ::close(socket); - env->SetIntField(thiz, gNative, -1); + env->SetIntField(thiz, gSocket, -1); } JNINativeMethod gMethods[] = { {"create", "(Ljava/lang/String;)I", (void *)create}, - {"dup", "()I", (void *)dup}, {"close", "()V", (void *)close}, }; @@ -114,7 +104,7 @@ int registerRtpStream(JNIEnv *env) { jclass clazz; if ((clazz = env->FindClass("android/net/rtp/RtpStream")) == NULL || - (gNative = env->GetFieldID(clazz, "mNative", "I")) == NULL || + (gSocket = env->GetFieldID(clazz, "mSocket", "I")) == NULL || env->RegisterNatives(clazz, gMethods, NELEM(gMethods)) < 0) { ALOGE("JNI registration failed"); return -1; diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java index a9dbd10b293e..3c761c802439 100644 --- a/wifi/java/android/net/wifi/WifiConfigStore.java +++ b/wifi/java/android/net/wifi/WifiConfigStore.java @@ -1141,7 +1141,15 @@ class WifiConfigStore { String varName = field.varName(); String value = field.value(); if (value != null) { - if (field != config.eap && field != config.engine) { + if (field == config.engine) { + /* + * If the field is declared as an integer, it must not + * be null + */ + if (value.length() == 0) { + value = "0"; + } + } else if (field != config.eap) { value = (value.length() == 0) ? "NULL" : convertToQuotedString(value); } if (!mWifiNative.setNetworkVariable( |