summaryrefslogtreecommitdiff
path: root/android/widget
diff options
context:
space:
mode:
authorJeff Davidson <jpd@google.com>2018-02-08 15:30:06 -0800
committerJeff Davidson <jpd@google.com>2018-02-08 15:30:06 -0800
commita192cc2a132cb0ee8588e2df755563ec7008c179 (patch)
tree380e4db22df19c819bd37df34bf06e7568916a50 /android/widget
parent98fe7819c6d14f4f464a5cac047f9e82dee5da58 (diff)
downloadandroid-28-a192cc2a132cb0ee8588e2df755563ec7008c179.tar.gz
Update fullsdk to 4575844
/google/data/ro/projects/android/fetch_artifact \ --bid 4575844 \ --target sdk_phone_x86_64-sdk \ sdk-repo-linux-sources-4575844.zip Test: TreeHugger Change-Id: I81e0eb157b4ac3b38408d0ef86f9d6286471f87a
Diffstat (limited to 'android/widget')
-rw-r--r--android/widget/AbsListView.java19
-rw-r--r--android/widget/AdapterView.java2
-rw-r--r--android/widget/CheckedTextView.java2
-rw-r--r--android/widget/CompoundButton.java2
-rw-r--r--android/widget/EditText.java4
-rw-r--r--android/widget/Editor.java44
-rw-r--r--android/widget/Magnifier.java41
-rw-r--r--android/widget/MediaControlView2.java279
-rw-r--r--android/widget/SelectionActionModeHelper.java34
-rw-r--r--android/widget/TextView.java314
-rw-r--r--android/widget/VideoView2.java602
11 files changed, 1275 insertions, 68 deletions
diff --git a/android/widget/AbsListView.java b/android/widget/AbsListView.java
index e0c897d3..594d2400 100644
--- a/android/widget/AbsListView.java
+++ b/android/widget/AbsListView.java
@@ -19,6 +19,7 @@ package android.widget;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.NonNull;
+import android.annotation.TestApi;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@@ -30,6 +31,7 @@ import android.graphics.drawable.TransitionDrawable;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
+import android.os.LocaleList;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.StrictMode;
@@ -2744,7 +2746,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
private void drawSelector(Canvas canvas) {
- if (!mSelectorRect.isEmpty()) {
+ if (shouldDrawSelector()) {
final Drawable selector = mSelector;
selector.setBounds(mSelectorRect);
selector.draw(canvas);
@@ -2752,6 +2754,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
/**
+ * @hide
+ */
+ @TestApi
+ public final boolean shouldDrawSelector() {
+ return !mSelectorRect.isEmpty();
+ }
+
+ /**
* Controls whether the selection highlight drawable should be drawn on top of the item or
* behind it.
*
@@ -6026,6 +6036,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
return getTarget().commitContent(inputContentInfo, flags, opts);
}
+
+ @Override
+ public void reportLanguageHint(@NonNull LocaleList languageHint) {
+ getTarget().reportLanguageHint(languageHint);
+ }
}
/**
@@ -6849,7 +6864,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// detached and we do not allow detached views to fire accessibility
// events. So we are announcing that the subtree changed giving a chance
// to clients holding on to a view in this subtree to refresh it.
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
// Don't scrap views that have transient state.
diff --git a/android/widget/AdapterView.java b/android/widget/AdapterView.java
index 6c192563..08374cb1 100644
--- a/android/widget/AdapterView.java
+++ b/android/widget/AdapterView.java
@@ -1093,7 +1093,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
checkSelectionChanged();
}
- notifySubtreeAccessibilityStateChangedIfNeeded();
+ notifyAccessibilitySubtreeChanged();
}
/**
diff --git a/android/widget/CheckedTextView.java b/android/widget/CheckedTextView.java
index 92bfd56d..af01a3eb 100644
--- a/android/widget/CheckedTextView.java
+++ b/android/widget/CheckedTextView.java
@@ -132,7 +132,7 @@ public class CheckedTextView extends TextView implements Checkable {
if (mChecked != checked) {
mChecked = checked;
refreshDrawableState();
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
}
diff --git a/android/widget/CompoundButton.java b/android/widget/CompoundButton.java
index 0762b156..e57f1536 100644
--- a/android/widget/CompoundButton.java
+++ b/android/widget/CompoundButton.java
@@ -158,7 +158,7 @@ public abstract class CompoundButton extends Button implements Checkable {
mCheckedFromResource = false;
mChecked = checked;
refreshDrawableState();
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
// Avoid infinite recursions if setChecked() is called from a listener
diff --git a/android/widget/EditText.java b/android/widget/EditText.java
index 336c20cd..728824c2 100644
--- a/android/widget/EditText.java
+++ b/android/widget/EditText.java
@@ -106,6 +106,10 @@ public class EditText extends TextView {
@Override
public Editable getText() {
CharSequence text = super.getText();
+ // This can only happen during construction.
+ if (text == null) {
+ return null;
+ }
if (text instanceof Editable) {
return (Editable) super.getText();
}
diff --git a/android/widget/Editor.java b/android/widget/Editor.java
index 05d18d18..7bb0db1c 100644
--- a/android/widget/Editor.java
+++ b/android/widget/Editor.java
@@ -2095,14 +2095,7 @@ public class Editor {
if (!(mTextView.getText() instanceof Spannable)) {
return;
}
- Spannable text = (Spannable) mTextView.getText();
stopTextActionMode();
- if (mTextView.isTextSelectable()) {
- Selection.setSelection((Spannable) text, link.getStart(), link.getEnd());
- } else {
- //TODO: Nonselectable text
- }
-
getSelectionActionModeHelper().startLinkActionModeAsync(link);
}
@@ -2179,7 +2172,8 @@ public class Editor {
return false;
}
- if (!checkField() || !mTextView.hasSelection()) {
+ if (actionMode != TextActionMode.TEXT_LINK
+ && (!checkField() || !mTextView.hasSelection())) {
return false;
}
@@ -3679,6 +3673,8 @@ public class Editor {
mIsShowingUp = true;
super.show();
}
+
+ mSuggestionListView.setVisibility(mNumberOfSuggestions == 0 ? View.GONE : View.VISIBLE);
}
@Override
@@ -4012,7 +4008,6 @@ public class Editor {
if (isValidAssistMenuItem(
textClassification.getIcon(),
textClassification.getLabel(),
- textClassification.getOnClickListener(),
textClassification.getIntent())) {
final MenuItem item = menu.add(
TextView.ID_ASSIST, TextView.ID_ASSIST, MENU_ITEM_ORDER_ASSIST,
@@ -4020,14 +4015,15 @@ public class Editor {
.setIcon(textClassification.getIcon())
.setIntent(textClassification.getIntent());
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
- mAssistClickHandlers.put(item, textClassification.getOnClickListener());
+ mAssistClickHandlers.put(
+ item, TextClassification.createStartActivityOnClickListener(
+ mTextView.getContext(), textClassification.getIntent()));
}
final int count = textClassification.getSecondaryActionsCount();
for (int i = 0; i < count; i++) {
if (!isValidAssistMenuItem(
textClassification.getSecondaryIcon(i),
textClassification.getSecondaryLabel(i),
- textClassification.getSecondaryOnClickListener(i),
textClassification.getSecondaryIntent(i))) {
continue;
}
@@ -4038,7 +4034,9 @@ public class Editor {
.setIcon(textClassification.getSecondaryIcon(i))
.setIntent(textClassification.getSecondaryIntent(i));
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
- mAssistClickHandlers.put(item, textClassification.getSecondaryOnClickListener(i));
+ mAssistClickHandlers.put(item,
+ TextClassification.createStartActivityOnClickListener(
+ mTextView.getContext(), textClassification.getSecondaryIntent(i)));
}
}
@@ -4054,10 +4052,9 @@ public class Editor {
}
}
- private boolean isValidAssistMenuItem(
- Drawable icon, CharSequence label, OnClickListener onClick, Intent intent) {
+ private boolean isValidAssistMenuItem(Drawable icon, CharSequence label, Intent intent) {
final boolean hasUi = icon != null || !TextUtils.isEmpty(label);
- final boolean hasAction = onClick != null || isSupportedIntent(intent);
+ final boolean hasAction = isSupportedIntent(intent);
return hasUi && hasAction;
}
@@ -4632,7 +4629,7 @@ public class Editor {
return 0;
}
- protected final void showMagnifier() {
+ protected final void showMagnifier(@NonNull final MotionEvent event) {
if (mMagnifier == null) {
return;
}
@@ -4658,9 +4655,10 @@ public class Editor {
final Layout layout = mTextView.getLayout();
final int lineNumber = layout.getLineForOffset(offset);
- // Horizontally snap to character offset.
- final float xPosInView = getHorizontal(mTextView.getLayout(), offset)
- + mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
+ // Horizontally move the magnifier smoothly.
+ final int[] textViewLocationOnScreen = new int[2];
+ mTextView.getLocationOnScreen(textViewLocationOnScreen);
+ final float xPosInView = event.getRawX() - textViewLocationOnScreen[0];
// Vertically snap to middle of current line.
final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber)
+ mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f
@@ -4855,11 +4853,11 @@ public class Editor {
case MotionEvent.ACTION_DOWN:
mDownPositionX = ev.getRawX();
mDownPositionY = ev.getRawY();
- showMagnifier();
+ showMagnifier(ev);
break;
case MotionEvent.ACTION_MOVE:
- showMagnifier();
+ showMagnifier(ev);
break;
case MotionEvent.ACTION_UP:
@@ -5213,11 +5211,11 @@ public class Editor {
// re-engages the handle.
mTouchWordDelta = 0.0f;
mPrevX = UNSET_X_VALUE;
- showMagnifier();
+ showMagnifier(event);
break;
case MotionEvent.ACTION_MOVE:
- showMagnifier();
+ showMagnifier(event);
break;
case MotionEvent.ACTION_UP:
diff --git a/android/widget/Magnifier.java b/android/widget/Magnifier.java
index 26dfcc2d..310b1708 100644
--- a/android/widget/Magnifier.java
+++ b/android/widget/Magnifier.java
@@ -32,6 +32,7 @@ import android.view.PixelCopy;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.View;
+import android.view.ViewParent;
import com.android.internal.util.Preconditions;
@@ -44,6 +45,8 @@ public final class Magnifier {
private static final int NONEXISTENT_PREVIOUS_CONFIG_VALUE = -1;
// The view to which this magnifier is attached.
private final View mView;
+ // The coordinates of the view in the surface.
+ private final int[] mViewCoordinatesInSurface;
// The window containing the magnifier.
private final PopupWindow mWindow;
// The center coordinates of the window containing the magnifier.
@@ -87,6 +90,8 @@ public final class Magnifier {
com.android.internal.R.dimen.magnifier_height);
mZoomScale = context.getResources().getFloat(
com.android.internal.R.dimen.magnifier_zoom_scale);
+ // The view's surface coordinates will not be updated until the magnifier is first shown.
+ mViewCoordinatesInSurface = new int[2];
mWindow = new PopupWindow(context);
mWindow.setContentView(content);
@@ -120,9 +125,34 @@ public final class Magnifier {
configureCoordinates(xPosInView, yPosInView);
// Clamp startX value to avoid distorting the rendering of the magnifier content.
- final int startX = Math.max(0, Math.min(
+ // For this, we compute:
+ // - zeroScrollXInSurface: this is the start x of mView, where this is not masked by a
+ // potential scrolling container. For example, if mView is a
+ // TextView contained in a HorizontalScrollView,
+ // mViewCoordinatesInSurface will reflect the surface position of
+ // the first text character, rather than the position of the first
+ // visible one. Therefore, we need to add back the amount of
+ // scrolling from the parent containers.
+ // - actualWidth: similarly, the width of a View will be larger than its actually visible
+ // width when it is contained in a scrolling container. We need to use
+ // the minimum width of a scrolling container which contains this view.
+ int zeroScrollXInSurface = mViewCoordinatesInSurface[0];
+ int actualWidth = mView.getWidth();
+ ViewParent viewParent = mView.getParent();
+ while (viewParent instanceof View) {
+ final View container = (View) viewParent;
+ if (container.canScrollHorizontally(-1 /* left scroll */)
+ || container.canScrollHorizontally(1 /* right scroll */)) {
+ zeroScrollXInSurface += container.getScrollX();
+ actualWidth = Math.min(actualWidth, container.getWidth()
+ - container.getPaddingLeft() - container.getPaddingRight());
+ }
+ viewParent = viewParent.getParent();
+ }
+
+ final int startX = Math.max(zeroScrollXInSurface, Math.min(
mCenterZoomCoords.x - mBitmap.getWidth() / 2,
- mView.getWidth() - mBitmap.getWidth()));
+ zeroScrollXInSurface + actualWidth - mBitmap.getWidth()));
final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
if (xPosInView != mPrevPosInView.x || yPosInView != mPrevPosInView.y) {
@@ -169,10 +199,9 @@ public final class Magnifier {
posX = xPosInView;
posY = yPosInView;
} else {
- final int[] coordinatesInSurface = new int[2];
- mView.getLocationInSurface(coordinatesInSurface);
- posX = xPosInView + coordinatesInSurface[0];
- posY = yPosInView + coordinatesInSurface[1];
+ mView.getLocationInSurface(mViewCoordinatesInSurface);
+ posX = xPosInView + mViewCoordinatesInSurface[0];
+ posY = yPosInView + mViewCoordinatesInSurface[1];
}
mCenterZoomCoords.x = Math.round(posX);
diff --git a/android/widget/MediaControlView2.java b/android/widget/MediaControlView2.java
new file mode 100644
index 00000000..f1d633a2
--- /dev/null
+++ b/android/widget/MediaControlView2.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2017 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.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.session.MediaController;
+import android.media.update.ApiLoader;
+import android.media.update.MediaControlView2Provider;
+import android.media.update.ViewProvider;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+/**
+ * A View that contains the controls for MediaPlayer2.
+ * It provides a wide range of UI including buttons such as "Play/Pause", "Rewind", "Fast Forward",
+ * "Subtitle", "Full Screen", and it is also possible to add multiple custom buttons.
+ *
+ * <p>
+ * <em> MediaControlView2 can be initialized in two different ways: </em>
+ * 1) When VideoView2 is initialized, it automatically initializes a MediaControlView2 instance and
+ * adds it to the view.
+ * 2) Initialize MediaControlView2 programmatically and add it to a ViewGroup instance.
+ *
+ * In the first option, VideoView2 automatically connects MediaControlView2 to MediaController2,
+ * which is necessary to communicate with MediaSession2. In the second option, however, the
+ * developer needs to manually retrieve a MediaController2 instance and set it to MediaControlView2
+ * by calling setController(MediaController2 controller).
+ *
+ * TODO PUBLIC API
+ * @hide
+ */
+public class MediaControlView2 extends FrameLayout {
+ /** @hide */
+ @IntDef({
+ BUTTON_PLAY_PAUSE,
+ BUTTON_FFWD,
+ BUTTON_REW,
+ BUTTON_NEXT,
+ BUTTON_PREV,
+ BUTTON_SUBTITLE,
+ BUTTON_FULL_SCREEN,
+ BUTTON_OVERFLOW,
+ BUTTON_MUTE,
+ BUTTON_ASPECT_RATIO,
+ BUTTON_SETTINGS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Button {}
+
+ public static final int BUTTON_PLAY_PAUSE = 1;
+ public static final int BUTTON_FFWD = 2;
+ public static final int BUTTON_REW = 3;
+ public static final int BUTTON_NEXT = 4;
+ public static final int BUTTON_PREV = 5;
+ public static final int BUTTON_SUBTITLE = 6;
+ public static final int BUTTON_FULL_SCREEN = 7;
+ public static final int BUTTON_OVERFLOW = 8;
+ public static final int BUTTON_MUTE = 9;
+ public static final int BUTTON_ASPECT_RATIO = 10;
+ public static final int BUTTON_SETTINGS = 11;
+
+ private final MediaControlView2Provider mProvider;
+
+ public MediaControlView2(@NonNull Context context) {
+ this(context, null);
+ }
+
+ public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public MediaControlView2(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ mProvider = ApiLoader.getProvider(context)
+ .createMediaControlView2(this, new SuperProvider());
+ }
+
+ /**
+ * @hide
+ */
+ public MediaControlView2Provider getProvider() {
+ return mProvider;
+ }
+
+ /**
+ * Sets MediaController2 instance to control corresponding MediaSession2.
+ */
+ public void setController(MediaController controller) {
+ mProvider.setController_impl(controller);
+ }
+
+ /**
+ * Shows the control view on screen. It will disappear automatically after 3 seconds of
+ * inactivity.
+ */
+ public void show() {
+ mProvider.show_impl();
+ }
+
+ /**
+ * Shows the control view on screen. It will disappear automatically after {@code timeout}
+ * milliseconds of inactivity.
+ */
+ public void show(int timeout) {
+ mProvider.show_impl(timeout);
+ }
+
+ /**
+ * Returns whether the control view is currently shown or hidden.
+ */
+ public boolean isShowing() {
+ return mProvider.isShowing_impl();
+ }
+
+ /**
+ * Hide the control view from the screen.
+ */
+ public void hide() {
+ mProvider.hide_impl();
+ }
+
+ /**
+ * If the media selected has a subtitle track, calling this method will display the subtitle at
+ * the bottom of the view. If a media has multiple subtitle tracks, this method will select the
+ * first one of them.
+ */
+ public void showSubtitle() {
+ mProvider.showSubtitle_impl();
+ }
+
+ /**
+ * Hides the currently displayed subtitle.
+ */
+ public void hideSubtitle() {
+ mProvider.hideSubtitle_impl();
+ }
+
+ /**
+ * Set listeners for previous and next buttons to customize the behavior of clicking them.
+ * The UI for these buttons are provided as default and will be automatically displayed when
+ * this method is called.
+ *
+ * @param next Listener for clicking next button
+ * @param prev Listener for clicking previous button
+ */
+ public void setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev) {
+ mProvider.setPrevNextListeners_impl(next, prev);
+ }
+
+ /**
+ * Hides the specified button from view.
+ *
+ * @param button the constant integer assigned to individual buttons
+ * @param visible whether the button should be visible or not
+ */
+ public void setButtonVisibility(int button, boolean visible) {
+ mProvider.setButtonVisibility_impl(button, visible);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ mProvider.onAttachedToWindow_impl();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ mProvider.onDetachedFromWindow_impl();
+ }
+
+ @Override
+ public CharSequence getAccessibilityClassName() {
+ return mProvider.getAccessibilityClassName_impl();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ return mProvider.onTouchEvent_impl(ev);
+ }
+
+ @Override
+ public boolean onTrackballEvent(MotionEvent ev) {
+ return mProvider.onTrackballEvent_impl(ev);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return mProvider.onKeyDown_impl(keyCode, event);
+ }
+
+ @Override
+ public void onFinishInflate() {
+ mProvider.onFinishInflate_impl();
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return mProvider.dispatchKeyEvent_impl(event);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ mProvider.setEnabled_impl(enabled);
+ }
+
+ private class SuperProvider implements ViewProvider {
+ @Override
+ public void onAttachedToWindow_impl() {
+ MediaControlView2.super.onAttachedToWindow();
+ }
+
+ @Override
+ public void onDetachedFromWindow_impl() {
+ MediaControlView2.super.onDetachedFromWindow();
+ }
+
+ @Override
+ public CharSequence getAccessibilityClassName_impl() {
+ return MediaControlView2.super.getAccessibilityClassName();
+ }
+
+ @Override
+ public boolean onTouchEvent_impl(MotionEvent ev) {
+ return MediaControlView2.super.onTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onTrackballEvent_impl(MotionEvent ev) {
+ return MediaControlView2.super.onTrackballEvent(ev);
+ }
+
+ @Override
+ public boolean onKeyDown_impl(int keyCode, KeyEvent event) {
+ return MediaControlView2.super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public void onFinishInflate_impl() {
+ MediaControlView2.super.onFinishInflate();
+ }
+
+ @Override
+ public boolean dispatchKeyEvent_impl(KeyEvent event) {
+ return MediaControlView2.super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public void setEnabled_impl(boolean enabled) {
+ MediaControlView2.super.setEnabled(enabled);
+ }
+ }
+}
diff --git a/android/widget/SelectionActionModeHelper.java b/android/widget/SelectionActionModeHelper.java
index 2c6466cd..3bfa520c 100644
--- a/android/widget/SelectionActionModeHelper.java
+++ b/android/widget/SelectionActionModeHelper.java
@@ -235,10 +235,13 @@ public final class SelectionActionModeHelper {
@Editor.TextActionMode int actionMode, @Nullable SelectionResult result) {
final CharSequence text = getText(mTextView);
if (result != null && text instanceof Spannable
- && (mTextView.isTextSelectable() || mTextView.isTextEditable())) {
+ && (mTextView.isTextSelectable()
+ || mTextView.isTextEditable()
+ || actionMode == Editor.TextActionMode.TEXT_LINK)) {
// Do not change the selection if TextClassifier should be dark launched.
if (!mTextView.getTextClassifier().getSettings().isDarkLaunch()) {
Selection.setSelection((Spannable) text, result.mStart, result.mEnd);
+ mTextView.invalidate();
}
mTextClassification = result.mClassification;
} else {
@@ -250,8 +253,17 @@ public final class SelectionActionModeHelper {
&& (mTextView.isTextSelectable() || mTextView.isTextEditable())) {
controller.show();
}
- if (result != null && actionMode == Editor.TextActionMode.SELECTION) {
- mSelectionTracker.onSmartSelection(result);
+ if (result != null) {
+ switch (actionMode) {
+ case Editor.TextActionMode.SELECTION:
+ mSelectionTracker.onSmartSelection(result);
+ break;
+ case Editor.TextActionMode.TEXT_LINK:
+ mSelectionTracker.onLinkSelected(result);
+ break;
+ default:
+ break;
+ }
}
}
mEditor.setRestartActionModeOnNextRefresh(false);
@@ -486,12 +498,24 @@ public final class SelectionActionModeHelper {
* Called when selection action mode is started and the results come from a classifier.
*/
public void onSmartSelection(SelectionResult result) {
+ onClassifiedSelection(result);
+ mLogger.logSelectionModified(
+ result.mStart, result.mEnd, result.mClassification, result.mSelection);
+ }
+
+ /**
+ * Called when link action mode is started and the classification comes from a classifier.
+ */
+ public void onLinkSelected(SelectionResult result) {
+ onClassifiedSelection(result);
+ // TODO: log (b/70246800)
+ }
+
+ private void onClassifiedSelection(SelectionResult result) {
if (isSelectionStarted()) {
mSelectionStart = result.mStart;
mSelectionEnd = result.mEnd;
mAllowReset = mSelectionStart != mOriginalStart || mSelectionEnd != mOriginalEnd;
- mLogger.logSelectionModified(
- result.mStart, result.mEnd, result.mClassification, result.mSelection);
}
}
diff --git a/android/widget/TextView.java b/android/widget/TextView.java
index 1e17f34a..7d3fcf46 100644
--- a/android/widget/TextView.java
+++ b/android/widget/TextView.java
@@ -27,8 +27,10 @@ import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.FloatRange;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.Px;
import android.annotation.Size;
import android.annotation.StringRes;
import android.annotation.StyleRes;
@@ -44,6 +46,7 @@ import android.content.UndoManager;
import android.content.res.ColorStateList;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
+import android.content.res.ResourceId;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -51,6 +54,7 @@ import android.graphics.BaseCanvas;
import android.graphics.Canvas;
import android.graphics.Insets;
import android.graphics.Paint;
+import android.graphics.Paint.FontMetricsInt;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.Rect;
@@ -76,8 +80,8 @@ import android.text.GraphicsOperations;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Layout;
+import android.text.MeasuredText;
import android.text.ParcelableSpan;
-import android.text.PremeasuredText;
import android.text.Selection;
import android.text.SpanWatcher;
import android.text.Spannable;
@@ -295,6 +299,7 @@ import java.util.Locale;
* @attr ref android.R.styleable#TextView_imeActionId
* @attr ref android.R.styleable#TextView_editorExtras
* @attr ref android.R.styleable#TextView_elegantTextHeight
+ * @attr ref android.R.styleable#TextView_fallbackLineSpacing
* @attr ref android.R.styleable#TextView_letterSpacing
* @attr ref android.R.styleable#TextView_fontFeatureSettings
* @attr ref android.R.styleable#TextView_breakStrategy
@@ -304,12 +309,12 @@ import java.util.Locale;
* @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
* @attr ref android.R.styleable#TextView_autoSizeStepGranularity
* @attr ref android.R.styleable#TextView_autoSizePresetSizes
+ * @attr ref android.R.styleable#TextView_accessibilityHeading
*/
@RemoteView
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
static final String LOG_TAG = "TextView";
static final boolean DEBUG_EXTRACT = false;
- static final boolean DEBUG_AUTOFILL = false;
private static final float[] TEMP_POSITION = new float[2];
// Enum for the "typeface" XML parameter.
@@ -399,6 +404,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private int mCurTextColor;
private int mCurHintTextColor;
private boolean mFreezesText;
+ private boolean mIsAccessibilityHeading;
private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
@@ -654,7 +660,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// True if internationalized input should be used for numbers and date and time.
private final boolean mUseInternationalizedInput;
// True if fallback fonts that end up getting used should be allowed to affect line spacing.
- /* package */ final boolean mUseFallbackLineSpacing;
+ /* package */ boolean mUseFallbackLineSpacing;
@ViewDebug.ExportedProperty(category = "text")
private int mGravity = Gravity.TOP | Gravity.START;
@@ -785,9 +791,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// mAutoSizeStepGranularityInPx.
private boolean mHasPresetAutoSizeValues = false;
- // Indicates whether the text was set from resources or dynamically, so it can be used to
+ // Indicates whether the text was set statically or dynamically, so it can be used to
// sanitize autofill requests.
- private boolean mTextFromResource = false;
+ private boolean mTextSetFromXmlOrResourceId = false;
+ // Resource id used to set the text - used for autofill purposes.
+ private @StringRes int mTextId = ResourceId.ID_NULL;
/**
* Kick-start the font cache for the zygote process (to pay the cost of
@@ -921,12 +929,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int inputType = EditorInfo.TYPE_NULL;
a = theme.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
+ int firstBaselineToTopHeight = -1;
+ int lastBaselineToBottomHeight = -1;
+ int lineHeight = -1;
readTextAppearance(context, a, attributes, true /* styleArray */);
int n = a.getIndexCount();
- boolean fromResourceId = false;
+ // Must set id in a temporary variable because it will be reset by setText()
+ boolean textIsSetFromXml = false;
for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
@@ -1068,7 +1080,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
case com.android.internal.R.styleable.TextView_text:
- fromResourceId = true;
+ textIsSetFromXml = true;
+ mTextId = a.getResourceId(attr, ResourceId.ID_NULL);
text = a.getText(attr);
break;
@@ -1244,6 +1257,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case com.android.internal.R.styleable.TextView_justificationMode:
mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE);
break;
+
+ case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight:
+ firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1);
+ break;
+
+ case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight:
+ lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1);
+ break;
+
+ case com.android.internal.R.styleable.TextView_lineHeight:
+ lineHeight = a.getDimensionPixelSize(attr, -1);
+ break;
+ case com.android.internal.R.styleable.TextView_accessibilityHeading:
+ mIsAccessibilityHeading = a.getBoolean(attr, false);
}
}
@@ -1460,8 +1487,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
setText(text, bufferType);
- if (fromResourceId) {
- mTextFromResource = true;
+ if (textIsSetFromXml) {
+ mTextSetFromXmlOrResourceId = true;
}
if (hint != null) setHint(hint);
@@ -1558,6 +1585,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} else {
mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE;
}
+
+ if (firstBaselineToTopHeight >= 0) {
+ setFirstBaselineToTopHeight(firstBaselineToTopHeight);
+ }
+ if (lastBaselineToBottomHeight >= 0) {
+ setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
+ }
+ if (lineHeight >= 0) {
+ setLineHeight(lineHeight);
+ }
}
/**
@@ -2360,7 +2397,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setText(mText);
if (hasPasswordTransformationMethod()) {
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
@@ -3160,6 +3197,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ /**
+ * @inheritDoc
+ *
+ * @see #setFirstBaselineToTopHeight(int)
+ * @see #setLastBaselineToBottomHeight(int)
+ */
@Override
public void setPadding(int left, int top, int right, int bottom) {
if (left != mPaddingLeft
@@ -3174,6 +3217,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
invalidate();
}
+ /**
+ * @inheritDoc
+ *
+ * @see #setFirstBaselineToTopHeight(int)
+ * @see #setLastBaselineToBottomHeight(int)
+ */
@Override
public void setPaddingRelative(int start, int top, int end, int bottom) {
if (start != getPaddingStart()
@@ -3189,6 +3238,97 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is
+ * equal to the distance between the firt text baseline and the top of this TextView.
+ * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was
+ * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated.
+ *
+ * @param firstBaselineToTopHeight distance between first baseline to top of the container
+ * in pixels
+ *
+ * @see #getFirstBaselineToTopHeight()
+ * @see #setPadding(int, int, int, int)
+ * @see #setPaddingRelative(int, int, int, int)
+ *
+ * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
+ */
+ public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) {
+ Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight);
+
+ final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
+ final int fontMetricsTop;
+ if (getIncludeFontPadding()) {
+ fontMetricsTop = fontMetrics.top;
+ } else {
+ fontMetricsTop = fontMetrics.ascent;
+ }
+
+ // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
+ // in settings). At the moment, we don't.
+
+ if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) {
+ final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop);
+ setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom());
+ }
+ }
+
+ /**
+ * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is
+ * equal to the distance between the last text baseline and the bottom of this TextView.
+ * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was
+ * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated.
+ *
+ * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container
+ * in pixels
+ *
+ * @see #getLastBaselineToBottomHeight()
+ * @see #setPadding(int, int, int, int)
+ * @see #setPaddingRelative(int, int, int, int)
+ *
+ * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
+ */
+ public void setLastBaselineToBottomHeight(
+ @Px @IntRange(from = 0) int lastBaselineToBottomHeight) {
+ Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight);
+
+ final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
+ final int fontMetricsBottom;
+ if (getIncludeFontPadding()) {
+ fontMetricsBottom = fontMetrics.bottom;
+ } else {
+ fontMetricsBottom = fontMetrics.descent;
+ }
+
+ // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size
+ // in settings). At the moment, we don't.
+
+ if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) {
+ final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom;
+ setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom);
+ }
+ }
+
+ /**
+ * Returns the distance between the first text baseline and the top of this TextView.
+ *
+ * @see #setFirstBaselineToTopHeight(int)
+ * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight
+ */
+ public int getFirstBaselineToTopHeight() {
+ return getPaddingTop() - getPaint().getFontMetricsInt().top;
+ }
+
+ /**
+ * Returns the distance between the last text baseline and the bottom of this TextView.
+ *
+ * @see #setLastBaselineToBottomHeight(int)
+ * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight
+ */
+ public int getLastBaselineToBottomHeight() {
+ return getPaddingBottom() + getPaint().getFontMetricsInt().bottom;
+ }
+
+ /**
* Gets the autolink mask of the text. See {@link
* android.text.util.Linkify#ALL Linkify.ALL} and peers for
* possible values.
@@ -3250,6 +3390,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0;
boolean mHasElegant = false;
boolean mElegant = false;
+ boolean mHasFallbackLineSpacing = false;
+ boolean mFallbackLineSpacing = false;
boolean mHasLetterSpacing = false;
float mLetterSpacing = 0;
String mFontFeatureSettings = null;
@@ -3274,6 +3416,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
+ " mShadowRadius:" + mShadowRadius + "\n"
+ " mHasElegant:" + mHasElegant + "\n"
+ " mElegant:" + mElegant + "\n"
+ + " mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n"
+ + " mFallbackLineSpacing:" + mFallbackLineSpacing + "\n"
+ " mHasLetterSpacing:" + mHasLetterSpacing + "\n"
+ " mLetterSpacing:" + mLetterSpacing + "\n"
+ " mFontFeatureSettings:" + mFontFeatureSettings + "\n"
@@ -3312,6 +3456,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
com.android.internal.R.styleable.TextAppearance_shadowRadius);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight,
com.android.internal.R.styleable.TextAppearance_elegantTextHeight);
+ sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing,
+ com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing,
com.android.internal.R.styleable.TextAppearance_letterSpacing);
sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings,
@@ -3402,6 +3548,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
attributes.mHasElegant = true;
attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant);
break;
+ case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing:
+ attributes.mHasFallbackLineSpacing = true;
+ attributes.mFallbackLineSpacing = appearance.getBoolean(attr,
+ attributes.mFallbackLineSpacing);
+ break;
case com.android.internal.R.styleable.TextAppearance_letterSpacing:
attributes.mHasLetterSpacing = true;
attributes.mLetterSpacing =
@@ -3455,6 +3606,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setElegantTextHeight(attributes.mElegant);
}
+ if (attributes.mHasFallbackLineSpacing) {
+ setFallbackLineSpacing(attributes.mFallbackLineSpacing);
+ }
+
if (attributes.mHasLetterSpacing) {
setLetterSpacing(attributes.mLetterSpacing);
}
@@ -3736,7 +3891,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*
* @param elegant set the paint's elegant metrics flag.
*
- * @see Paint#isElegantTextHeight(boolean)
+ * @see #isElegantTextHeight()
+ * @see Paint#isElegantTextHeight()
*
* @attr ref android.R.styleable#TextView_elegantTextHeight
*/
@@ -3752,6 +3908,43 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Set whether to respect the ascent and descent of the fallback fonts that are used in
+ * displaying the text (which is needed to avoid text from consecutive lines running into
+ * each other). If set, fallback fonts that end up getting used can increase the ascent
+ * and descent of the lines that they are used on.
+ * <p/>
+ * It is required to be true if text could be in languages like Burmese or Tibetan where text
+ * is typically much taller or deeper than Latin text.
+ *
+ * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default
+ *
+ * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean)
+ *
+ * @attr ref android.R.styleable#TextView_fallbackLineSpacing
+ */
+ public void setFallbackLineSpacing(boolean enabled) {
+ if (mUseFallbackLineSpacing != enabled) {
+ mUseFallbackLineSpacing = enabled;
+ if (mLayout != null) {
+ nullLayouts();
+ requestLayout();
+ invalidate();
+ }
+ }
+ }
+
+ /**
+ * @return whether fallback line spacing is enabled, {@code true} by default
+ *
+ * @see #setFallbackLineSpacing(boolean)
+ *
+ * @attr ref android.R.styleable#TextView_fallbackLineSpacing
+ */
+ public boolean isFallbackLineSpacing() {
+ return mUseFallbackLineSpacing;
+ }
+
+ /**
* Get the value of the TextView's elegant height metrics flag. This setting selects font
* variants that have not been compacted to fit Latin-based vertical
* metrics, and also increases top and bottom bounds to provide more space.
@@ -4917,6 +5110,53 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Sets an explicit line height for this TextView. This is equivalent to the vertical distance
+ * between subsequent baselines in the TextView.
+ *
+ * @param lineHeight the line height in pixels
+ *
+ * @see #setLineSpacing(float, float)
+ * @see #getLineSpacing()
+ *
+ * @attr ref android.R.styleable#TextView_lineHeight
+ */
+ public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) {
+ Preconditions.checkArgumentNonnegative(lineHeight);
+
+ final int fontHeight = getPaint().getFontMetricsInt(null);
+ // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw.
+ if (lineHeight != fontHeight) {
+ // Set lineSpacingExtra by the difference of lineSpacing with lineHeight
+ setLineSpacing(lineHeight - fontHeight, 1f);
+ }
+ }
+
+ /**
+ * Gets whether this view is a heading for accessibility purposes.
+ *
+ * @return {@code true} if the view is a heading, {@code false} otherwise.
+ *
+ * @attr ref android.R.styleable#TextView_accessibilityHeading
+ */
+ public boolean isAccessibilityHeading() {
+ return mIsAccessibilityHeading;
+ }
+
+ /**
+ * Set if view is a heading for a section of content for accessibility purposes.
+ *
+ * @param isHeading {@code true} if the view is a heading, {@code false} otherwise.
+ *
+ * @attr ref android.R.styleable#TextView_accessibilityHeading
+ */
+ public void setAccessibilityHeading(boolean isHeading) {
+ if (isHeading != mIsAccessibilityHeading) {
+ mIsAccessibilityHeading = isHeading;
+ notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+ }
+ }
+
+ /**
* Convenience method to append the specified text to the TextView's
* display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
* if it was not already editable.
@@ -5278,7 +5518,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private void setText(CharSequence text, BufferType type,
boolean notifyBefore, int oldlen) {
- mTextFromResource = false;
+ mTextSetFromXmlOrResourceId = false;
if (text == null) {
text = "";
}
@@ -5336,7 +5576,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (imm != null) imm.restartInput(this);
} else if (type == BufferType.SPANNABLE || mMovement != null) {
text = mSpannableFactory.newSpannable(text);
- } else if (!(text instanceof PremeasuredText || text instanceof CharWrapper)) {
+ } else if (!(text instanceof MeasuredText || text instanceof CharWrapper)) {
text = TextUtils.stringOrSpannedString(text);
}
@@ -5419,7 +5659,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
sendOnTextChanged(text, 0, oldlen, textLength);
onTextChanged(text, 0, oldlen, textLength);
- notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
+ notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
if (needEditableForNotification) {
sendAfterTextChanged((Editable) text);
@@ -5516,7 +5756,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@android.view.RemotableViewMethod
public final void setText(@StringRes int resid) {
setText(getContext().getResources().getText(resid));
- mTextFromResource = true;
+ mTextSetFromXmlOrResourceId = true;
+ mTextId = resid;
}
/**
@@ -5543,7 +5784,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
public final void setText(@StringRes int resid, BufferType type) {
setText(getContext().getResources().getText(resid), type);
- mTextFromResource = true;
+ mTextSetFromXmlOrResourceId = true;
+ mTextId = resid;
}
/**
@@ -6151,7 +6393,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void setError(CharSequence error, Drawable icon) {
createEditorIfNeeded();
mEditor.setError(error, icon);
- notifyViewAccessibilityStateChangedIfNeeded(
+ notifyAccessibilityStateChanged(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
@@ -9066,8 +9308,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
*
- * Checks whether the transformation method applied to this TextView is set to ALL CAPS. This
- * settings is internally ignored if this field is editable or selectable.
+ * Checks whether the transformation method applied to this TextView is set to ALL CAPS.
* @return Whether the current transformation method is for ALL CAPS.
*
* @see #setAllCaps(boolean)
@@ -9456,7 +9697,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
final AutofillManager afm = mContext.getSystemService(AutofillManager.class);
if (afm != null) {
- if (DEBUG_AUTOFILL) {
+ if (android.view.autofill.Helper.sVerbose) {
Log.v(LOG_TAG, "sendAfterTextChanged(): notify AFM for text=" + mText);
}
afm.notifyValueChanged(TextView.this);
@@ -10234,7 +10475,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final boolean isPassword = hasPasswordTransformationMethod()
|| isPasswordInputType(getInputType());
if (forAutofill) {
- structure.setDataIsSensitive(!mTextFromResource);
+ structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId);
+ if (mTextId != ResourceId.ID_NULL) {
+ try {
+ structure.setTextIdEntry(getResources().getResourceEntryName(mTextId));
+ } catch (Resources.NotFoundException e) {
+ if (android.view.autofill.Helper.sVerbose) {
+ Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id "
+ + mTextId + ": " + e.getMessage());
+ }
+ }
+ }
}
if (!isPassword || forAutofill) {
@@ -10455,6 +10706,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
info.setText(getTextForAccessibility());
info.setHintText(mHint);
info.setShowingHintText(isShowingHint());
+ info.setHeading(mIsAccessibilityHeading);
if (mBufferType == BufferType.EDITABLE) {
info.setEditable(true);
@@ -10942,6 +11194,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return true;
case ID_COPY:
+ // For link action mode in a non-selectable/non-focusable TextView,
+ // make sure that we set the appropriate min/max.
+ final int selStart = getSelectionStart();
+ final int selEnd = getSelectionEnd();
+ min = Math.max(0, Math.min(selStart, selEnd));
+ max = Math.max(0, Math.max(selStart, selEnd));
final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max));
if (setPrimaryClip(copyData)) {
stopTextActionMode();
@@ -11164,11 +11422,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
public boolean requestActionMode(@NonNull TextLinks.TextLink link) {
Preconditions.checkNotNull(link);
- if (mEditor != null) {
- mEditor.startLinkActionModeAsync(link);
- return true;
- }
- return false;
+ createEditorIfNeeded();
+ mEditor.startLinkActionModeAsync(link);
+ return true;
}
/**
* @hide
@@ -11883,7 +12139,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private final Choreographer mChoreographer;
private byte mStatus = MARQUEE_STOPPED;
- private final float mPixelsPerSecond;
+ private final float mPixelsPerMs;
private float mMaxScroll;
private float mMaxFadeScroll;
private float mGhostStart;
@@ -11896,7 +12152,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Marquee(TextView v) {
final float density = v.getContext().getResources().getDisplayMetrics().density;
- mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
+ mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f;
mView = new WeakReference<TextView>(v);
mChoreographer = Choreographer.getInstance();
}
@@ -11941,7 +12197,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
long currentMs = mChoreographer.getFrameTime();
long deltaMs = currentMs - mLastAnimationMs;
mLastAnimationMs = currentMs;
- float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
+ float deltaPx = deltaMs * mPixelsPerMs;
mScroll += deltaPx;
if (mScroll > mMaxScroll) {
mScroll = mMaxScroll;
diff --git a/android/widget/VideoView2.java b/android/widget/VideoView2.java
new file mode 100644
index 00000000..8650c0a0
--- /dev/null
+++ b/android/widget/VideoView2.java
@@ -0,0 +1,602 @@
+/*
+ * Copyright 2018 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.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.MediaPlayerBase;
+import android.media.update.ApiLoader;
+import android.media.update.VideoView2Provider;
+import android.media.update.ViewProvider;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Map;
+
+// TODO: Use @link tag to refer MediaPlayer2 in docs once MediaPlayer2.java is submitted. Same to
+// MediaSession2.
+// TODO: change the reference from MediaPlayer to MediaPlayer2.
+/**
+ * Displays a video file. VideoView2 class is a View class which is wrapping MediaPlayer2 so that
+ * developers can easily implement a video rendering application.
+ *
+ * <p>
+ * <em> Data sources that VideoView2 supports : </em>
+ * VideoView2 can play video files and audio-only fiels as
+ * well. It can load from various sources such as resources or content providers. The supported
+ * media file formats are the same as MediaPlayer2.
+ *
+ * <p>
+ * <em> View type can be selected : </em>
+ * VideoView2 can render videos on top of TextureView as well as
+ * SurfaceView selectively. The default is SurfaceView and it can be changed using
+ * {@link #setViewType(int)} method. Using SurfaceView is recommended in most cases for saving
+ * battery. TextureView might be preferred for supporting various UIs such as animation and
+ * translucency.
+ *
+ * <p>
+ * <em> Differences between {@link VideoView} class : </em>
+ * VideoView2 covers and inherits the most of
+ * VideoView's functionalities. The main differences are
+ * <ul>
+ * <li> VideoView2 inherits FrameLayout and renders videos using SurfaceView and TextureView
+ * selectively while VideoView inherits SurfaceView class.
+ * <li> VideoView2 is integrated with MediaControlView2 and a default MediaControlView2 instance is
+ * attached to VideoView2 by default. If a developer does not want to use the default
+ * MediaControlView2, needs to set enableControlView attribute to false. For instance,
+ * <pre>
+ * &lt;VideoView2
+ * android:id="@+id/video_view"
+ * xmlns:widget="http://schemas.android.com/apk/com.android.media.update"
+ * widget:enableControlView="false" /&gt;
+ * </pre>
+ * If a developer wants to attach a customed MediaControlView2, then set enableControlView attribute
+ * to false and assign the customed media control widget using {@link #setMediaControlView2}.
+ * <li> VideoView2 is integrated with MediaPlayer2 while VideoView is integrated with MediaPlayer.
+ * <li> VideoView2 is integrated with MediaSession2 and so it responses with media key events.
+ * A VideoView2 keeps a MediaSession2 instance internally and connects it to a corresponding
+ * MediaControlView2 instance.
+ * </p>
+ * </ul>
+ *
+ * <p>
+ * <em> Audio focus and audio attributes : </em>
+ * By default, VideoView2 requests audio focus with
+ * {@link AudioManager#AUDIOFOCUS_GAIN}. Use {@link #setAudioFocusRequest(int)} to change this
+ * behavior. The default {@link AudioAttributes} used during playback have a usage of
+ * {@link AudioAttributes#USAGE_MEDIA} and a content type of
+ * {@link AudioAttributes#CONTENT_TYPE_MOVIE}, use {@link #setAudioAttributes(AudioAttributes)} to
+ * modify them.
+ *
+ * <p>
+ * Note: VideoView2 does not retain its full state when going into the background. In particular, it
+ * does not restore the current play state, play position, selected tracks. Applications should save
+ * and restore these on their own in {@link android.app.Activity#onSaveInstanceState} and
+ * {@link android.app.Activity#onRestoreInstanceState}.
+ *
+ * @hide
+ */
+public class VideoView2 extends FrameLayout {
+ /** @hide */
+ @IntDef({
+ VIEW_TYPE_TEXTUREVIEW,
+ VIEW_TYPE_SURFACEVIEW
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ViewType {}
+
+ public static final int VIEW_TYPE_SURFACEVIEW = 1;
+ public static final int VIEW_TYPE_TEXTUREVIEW = 2;
+
+ private final VideoView2Provider mProvider;
+
+ public VideoView2(@NonNull Context context) {
+ this(context, null);
+ }
+
+ public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public VideoView2(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public VideoView2(
+ @NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ mProvider = ApiLoader.getProvider(context).createVideoView2(this, new SuperProvider(),
+ attrs, defStyleAttr, defStyleRes);
+ }
+
+ /**
+ * @hide
+ */
+ public VideoView2Provider getProvider() {
+ return mProvider;
+ }
+
+ /**
+ * Sets MediaControlView2 instance. It will replace the previously assigned MediaControlView2
+ * instance if any.
+ *
+ * @param mediaControlView a media control view2 instance.
+ */
+ public void setMediaControlView2(MediaControlView2 mediaControlView) {
+ mProvider.setMediaControlView2_impl(mediaControlView);
+ }
+
+ /**
+ * Returns MediaControlView2 instance which is currently attached to VideoView2 by default or by
+ * {@link #setMediaControlView2} method.
+ */
+ public MediaControlView2 getMediaControlView2() {
+ return mProvider.getMediaControlView2_impl();
+ }
+
+ /**
+ * Starts playback with the media contents specified by {@link #setVideoURI} and
+ * {@link #setVideoPath}.
+ * If it has been paused, this method will resume playback from the current position.
+ */
+ public void start() {
+ mProvider.start_impl();
+ }
+
+ /**
+ * Pauses playback.
+ */
+ public void pause() {
+ mProvider.pause_impl();
+ }
+
+ /**
+ * Gets the duration of the media content specified by #setVideoURI and #setVideoPath
+ * in milliseconds.
+ */
+ public int getDuration() {
+ return mProvider.getDuration_impl();
+ }
+
+ /**
+ * Gets current playback position in milliseconds.
+ */
+ public int getCurrentPosition() {
+ return mProvider.getCurrentPosition_impl();
+ }
+
+ // TODO: mention about key-frame related behavior.
+ /**
+ * Moves the media by specified time position.
+ * @param msec the offset in milliseconds from the start to seek to.
+ */
+ public void seekTo(int msec) {
+ mProvider.seekTo_impl(msec);
+ }
+
+ /**
+ * Says if the media is currently playing.
+ * @return true if the media is playing, false if it is not (eg. paused or stopped).
+ */
+ public boolean isPlaying() {
+ return mProvider.isPlaying_impl();
+ }
+
+ // TODO: check what will return if it is a local media.
+ /**
+ * Gets the percentage (0-100) of the content that has been buffered or played so far.
+ */
+ public int getBufferPercentage() {
+ return mProvider.getBufferPercentage_impl();
+ }
+
+ /**
+ * Returns the audio session ID.
+ */
+ public int getAudioSessionId() {
+ return mProvider.getAudioSessionId_impl();
+ }
+
+ /**
+ * Starts rendering closed caption or subtitles if there is any. The first subtitle track will
+ * be chosen by default if there multiple subtitle tracks exist.
+ */
+ public void showSubtitle() {
+ mProvider.showSubtitle_impl();
+ }
+
+ /**
+ * Stops showing closed captions or subtitles.
+ */
+ public void hideSubtitle() {
+ mProvider.hideSubtitle_impl();
+ }
+
+ /**
+ * Sets full screen mode.
+ */
+ public void setFullScreen(boolean fullScreen) {
+ mProvider.setFullScreen_impl(fullScreen);
+ }
+
+ // TODO: This should be revised after integration with MediaPlayer2.
+ /**
+ * Sets playback speed.
+ *
+ * It is expressed as a multiplicative factor, where normal speed is 1.0f. If it is less than
+ * or equal to zero, it will be just ignored and nothing will be changed. If it exceeds the
+ * maximum speed that internal engine supports, system will determine best handling or it will
+ * be reset to the normal speed 1.0f.
+ * @param speed the playback speed. It should be positive.
+ */
+ public void setSpeed(float speed) {
+ mProvider.setSpeed_impl(speed);
+ }
+
+ /**
+ * Returns current speed setting.
+ *
+ * If setSpeed() has never been called, returns the default value 1.0f.
+ * @return current speed setting
+ */
+ public float getSpeed() {
+ return mProvider.getSpeed_impl();
+ }
+
+ /**
+ * Sets which type of audio focus will be requested during the playback, or configures playback
+ * to not request audio focus. Valid values for focus requests are
+ * {@link AudioManager#AUDIOFOCUS_GAIN}, {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT},
+ * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK}, and
+ * {@link AudioManager#AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}. Or use
+ * {@link AudioManager#AUDIOFOCUS_NONE} to express that audio focus should not be
+ * requested when playback starts. You can for instance use this when playing a silent animation
+ * through this class, and you don't want to affect other audio applications playing in the
+ * background.
+ *
+ * @param focusGain the type of audio focus gain that will be requested, or
+ * {@link AudioManager#AUDIOFOCUS_NONE} to disable the use audio focus during
+ * playback.
+ */
+ public void setAudioFocusRequest(int focusGain) {
+ mProvider.setAudioFocusRequest_impl(focusGain);
+ }
+
+ /**
+ * Sets the {@link AudioAttributes} to be used during the playback of the video.
+ *
+ * @param attributes non-null <code>AudioAttributes</code>.
+ */
+ public void setAudioAttributes(@NonNull AudioAttributes attributes) {
+ mProvider.setAudioAttributes_impl(attributes);
+ }
+
+ /**
+ * Sets a remote player for handling playback of the selected route from MediaControlView2.
+ * If this is not called, MediaCotrolView2 will not show the route button.
+ *
+ * @param routeCategories the list of media control categories in
+ * {@link android.support.v7.media.MediaControlIntent}
+ * @param player the player to handle the selected route. If null, a default
+ * route player will be used.
+ * @throws IllegalStateException if MediaControlView2 is not set.
+ */
+ public void setRouteAttributes(@NonNull List<String> routeCategories,
+ @Nullable MediaPlayerBase player) {
+ mProvider.setRouteAttributes_impl(routeCategories, player);
+ }
+
+ /**
+ * Sets video path.
+ *
+ * @param path the path of the video.
+ */
+ public void setVideoPath(String path) {
+ mProvider.setVideoPath_impl(path);
+ }
+
+ /**
+ * Sets video URI.
+ *
+ * @param uri the URI of the video.
+ */
+ public void setVideoURI(Uri uri) {
+ mProvider.setVideoURI_impl(uri);
+ }
+
+ /**
+ * Sets video URI using specific headers.
+ *
+ * @param uri the URI of the video.
+ * @param headers the headers for the URI request.
+ * Note that the cross domain redirection is allowed by default, but that can be
+ * changed with key/value pairs through the headers parameter with
+ * "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
+ * to disallow or allow cross domain redirection.
+ */
+ public void setVideoURI(Uri uri, Map<String, String> headers) {
+ mProvider.setVideoURI_impl(uri, headers);
+ }
+
+ /**
+ * Selects which view will be used to render video between SurfacView and TextureView.
+ *
+ * @param viewType the view type to render video
+ * <ul>
+ * <li>{@link #VIEW_TYPE_SURFACEVIEW}
+ * <li>{@link #VIEW_TYPE_TEXTUREVIEW}
+ * </ul>
+ */
+ public void setViewType(@ViewType int viewType) {
+ mProvider.setViewType_impl(viewType);
+ }
+
+ /**
+ * Returns view type.
+ *
+ * @return view type. See {@see setViewType}.
+ */
+ @ViewType
+ public int getViewType() {
+ return mProvider.getViewType_impl();
+ }
+
+ /**
+ * Stops playback and release all the resources. This should be called whenever a VideoView2
+ * instance is no longer to be used.
+ */
+ public void stopPlayback() {
+ mProvider.stopPlayback_impl();
+ }
+
+ /**
+ * Registers a callback to be invoked when the media file is loaded and ready to go.
+ *
+ * @param l the callback that will be run.
+ */
+ public void setOnPreparedListener(OnPreparedListener l) {
+ mProvider.setOnPreparedListener_impl(l);
+ }
+
+ /**
+ * Registers a callback to be invoked when the end of a media file has been reached during
+ * playback.
+ *
+ * @param l the callback that will be run.
+ */
+ public void setOnCompletionListener(OnCompletionListener l) {
+ mProvider.setOnCompletionListener_impl(l);
+ }
+
+ /**
+ * Registers a callback to be invoked when an error occurs during playback or setup. If no
+ * listener is specified, or if the listener returned false, VideoView2 will inform the user of
+ * any errors.
+ *
+ * @param l The callback that will be run
+ */
+ public void setOnErrorListener(OnErrorListener l) {
+ mProvider.setOnErrorListener_impl(l);
+ }
+
+ /**
+ * Registers a callback to be invoked when an informational event occurs during playback or
+ * setup.
+ *
+ * @param l The callback that will be run
+ */
+ public void setOnInfoListener(OnInfoListener l) {
+ mProvider.setOnInfoListener_impl(l);
+ }
+
+ /**
+ * Registers a callback to be invoked when a view type change is done.
+ * {@see #setViewType(int)}
+ * @param l The callback that will be run
+ */
+ public void setOnViewTypeChangedListener(OnViewTypeChangedListener l) {
+ mProvider.setOnViewTypeChangedListener_impl(l);
+ }
+
+ /**
+ * Registers a callback to be invoked when the fullscreen mode should be changed.
+ */
+ public void setFullScreenChangedListener(OnFullScreenChangedListener l) {
+ mProvider.setFullScreenChangedListener_impl(l);
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when the viw type has been changed.
+ */
+ public interface OnViewTypeChangedListener {
+ /**
+ * Called when the view type has been changed.
+ * @see #setViewType(int)
+ * @param viewType
+ * <ul>
+ * <li>{@link #VIEW_TYPE_SURFACEVIEW}
+ * <li>{@link #VIEW_TYPE_TEXTUREVIEW}
+ * </ul>
+ */
+ void onViewTypeChanged(@ViewType int viewType);
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when the media source is ready for playback.
+ */
+ public interface OnPreparedListener {
+ /**
+ * Called when the media file is ready for playback.
+ */
+ void onPrepared();
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when playback of a media source has
+ * completed.
+ */
+ public interface OnCompletionListener {
+ /**
+ * Called when the end of a media source is reached during playback.
+ */
+ void onCompletion();
+ }
+
+ /**
+ * Interface definition of a callback to be invoked when there has been an error during an
+ * asynchronous operation.
+ */
+ public interface OnErrorListener {
+ // TODO: Redefine error codes.
+ /**
+ * Called to indicate an error.
+ * @param what the type of error that has occurred
+ * @param extra an extra code, specific to the error.
+ * @return true if the method handled the error, false if it didn't.
+ * @see MediaPlayer#OnErrorListener
+ */
+ boolean onError(int what, int extra);
+ }
+
+ /**
+ * Interface definition of a callback to be invoked to communicate some info and/or warning
+ * about the media or its playback.
+ */
+ public interface OnInfoListener {
+ /**
+ * Called to indicate an info or a warning.
+ * @param what the type of info or warning.
+ * @param extra an extra code, specific to the info.
+ *
+ * @see MediaPlayer#OnInfoListener
+ */
+ void onInfo(int what, int extra);
+ }
+
+ /**
+ * Interface definition of a callback to be invoked to inform the fullscreen mode is changed.
+ */
+ public interface OnFullScreenChangedListener {
+ /**
+ * Called to indicate a fullscreen mode change.
+ */
+ void onFullScreenChanged(boolean fullScreen);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ mProvider.onAttachedToWindow_impl();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ mProvider.onDetachedFromWindow_impl();
+ }
+
+ @Override
+ public CharSequence getAccessibilityClassName() {
+ return mProvider.getAccessibilityClassName_impl();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ return mProvider.onTouchEvent_impl(ev);
+ }
+
+ @Override
+ public boolean onTrackballEvent(MotionEvent ev) {
+ return mProvider.onTrackballEvent_impl(ev);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return mProvider.onKeyDown_impl(keyCode, event);
+ }
+
+ @Override
+ public void onFinishInflate() {
+ mProvider.onFinishInflate_impl();
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ return mProvider.dispatchKeyEvent_impl(event);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ mProvider.setEnabled_impl(enabled);
+ }
+
+ private class SuperProvider implements ViewProvider {
+ @Override
+ public void onAttachedToWindow_impl() {
+ VideoView2.super.onAttachedToWindow();
+ }
+
+ @Override
+ public void onDetachedFromWindow_impl() {
+ VideoView2.super.onDetachedFromWindow();
+ }
+
+ @Override
+ public CharSequence getAccessibilityClassName_impl() {
+ return VideoView2.super.getAccessibilityClassName();
+ }
+
+ @Override
+ public boolean onTouchEvent_impl(MotionEvent ev) {
+ return VideoView2.super.onTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onTrackballEvent_impl(MotionEvent ev) {
+ return VideoView2.super.onTrackballEvent(ev);
+ }
+
+ @Override
+ public boolean onKeyDown_impl(int keyCode, KeyEvent event) {
+ return VideoView2.super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public void onFinishInflate_impl() {
+ VideoView2.super.onFinishInflate();
+ }
+
+ @Override
+ public boolean dispatchKeyEvent_impl(KeyEvent event) {
+ return VideoView2.super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public void setEnabled_impl(boolean enabled) {
+ VideoView2.super.setEnabled(enabled);
+ }
+ }
+}