summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorClara Bayarri <clarabayarri@google.com>2015-03-27 17:32:45 +0000
committerClara Bayarri <clarabayarri@google.com>2015-04-10 16:28:35 +0100
commitd5bf3ed9b0138e9fd305da91386d1df03f9a75cc (patch)
tree5f38f58ba996c7f01ff620c55a3e64f10a8fe4ce /core
parent4c42bc045daccb293198559334df6fc6232fff6a (diff)
downloadframeworks_base-d5bf3ed9b0138e9fd305da91386d1df03f9a75cc.zip
frameworks_base-d5bf3ed9b0138e9fd305da91386d1df03f9a75cc.tar.gz
frameworks_base-d5bf3ed9b0138e9fd305da91386d1df03f9a75cc.tar.bz2
Show and trigger activities that implement Text Processing actions
This CL adds the Activities that support Intent.ACTION_PROCESS_TEXT to the Text Selection Action Mode in Editor, and triggers an intent with the currently selected text when they are selected. It also adds the required mechanism to allow a View to request an intent to be started, and return the activity result back to it. Change-Id: I62ec618010edf01da41338c8c1a7dd4292a15227
Diffstat (limited to 'core')
-rw-r--r--core/java/android/app/Activity.java46
-rw-r--r--core/java/android/app/Instrumentation.java9
-rw-r--r--core/java/android/content/Context.java28
-rw-r--r--core/java/android/content/ContextWrapper.java11
-rw-r--r--core/java/android/view/View.java77
-rw-r--r--core/java/android/view/ViewGroup.java20
-rw-r--r--core/java/android/widget/Editor.java37
-rw-r--r--core/java/android/widget/TextView.java47
8 files changed, 261 insertions, 14 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index b5817df..a0d59d9 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -94,6 +94,7 @@ import android.view.View.OnCreateContextMenuListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewManager;
+import android.view.ViewRootImpl;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -4467,21 +4468,38 @@ public class Activity extends ContextThemeWrapper
*/
public void startActivityFromFragment(@NonNull Fragment fragment, Intent intent,
int requestCode, @Nullable Bundle options) {
+ startActivityForResult(fragment.mWho, intent, requestCode, options);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void startActivityForResult(
+ String who, Intent intent, int requestCode, @Nullable Bundle options) {
if (options != null) {
mActivityTransitionState.startExitOutTransition(this, options);
}
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
- this, mMainThread.getApplicationThread(), mToken, fragment,
+ this, mMainThread.getApplicationThread(), mToken, who,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
- mToken, fragment.mWho, requestCode,
+ mToken, who, requestCode,
ar.getResultCode(), ar.getResultData());
}
}
/**
+ * @hide
+ */
+ @Override
+ public boolean canStartActivityForResult() {
+ return true;
+ }
+
+ /**
* Same as calling {@link #startIntentSenderFromChild(Activity, IntentSender,
* int, Intent, int, int, int, Bundle)} with no options.
*/
@@ -6364,12 +6382,24 @@ public class Activity extends ContextThemeWrapper
onActivityResult(requestCode, resultCode, data);
}
} else {
- Fragment frag = mFragments.findFragmentByWho(who);
- if (frag != null) {
- if (isRequestPermissionResult(data)) {
- dispatchRequestPermissionsResultToFragment(requestCode, data, frag);
- } else {
- frag.onActivityResult(requestCode, resultCode, data);
+ if (who.startsWith("@android:view:")) {
+ ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews(
+ getActivityToken());
+ for (ViewRootImpl viewRoot : views) {
+ if (viewRoot.getView() != null
+ && viewRoot.getView().dispatchActivityResult(
+ who, requestCode, resultCode, data)) {
+ return;
+ }
+ }
+ } else {
+ Fragment frag = mFragments.findFragmentByWho(who);
+ if (frag != null) {
+ if (isRequestPermissionResult(data)) {
+ dispatchRequestPermissionsResultToFragment(requestCode, data, frag);
+ } else {
+ frag.onActivityResult(requestCode, resultCode, data);
+ }
}
}
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 5572d30..fc96464 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1568,7 +1568,7 @@ public class Instrumentation {
/**
* Like {@link #execStartActivity(android.content.Context, android.os.IBinder,
- * android.os.IBinder, Fragment, android.content.Intent, int, android.os.Bundle)},
+ * android.os.IBinder, String, android.content.Intent, int, android.os.Bundle)},
* but for calls from a {#link Fragment}.
*
* @param who The Context from which the activity is being started.
@@ -1576,7 +1576,7 @@ public class Instrumentation {
* is being started.
* @param token Internal token identifying to the system who is starting
* the activity; may be null.
- * @param target Which fragment is performing the start (and thus receiving
+ * @param target Which element is performing the start (and thus receiving
* any result).
* @param intent The actual Intent to start.
* @param requestCode Identifier for this request's result; less than zero
@@ -1595,7 +1595,7 @@ public class Instrumentation {
* {@hide}
*/
public ActivityResult execStartActivity(
- Context who, IBinder contextThread, IBinder token, Fragment target,
+ Context who, IBinder contextThread, IBinder token, String target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
if (mActivityMonitors != null) {
@@ -1619,8 +1619,7 @@ public class Instrumentation {
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
- token, target != null ? target.mWho : null,
- requestCode, 0, null, options);
+ token, target, requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 9450dce..5cd8025 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1298,6 +1298,34 @@ public abstract class Context {
}
/**
+ * Version of {@link #startActivity(Intent, Bundle)} that returns a result to the caller. This
+ * is only supported for Views and Fragments.
+ * @param who The identifier for the calling element that will receive the result.
+ * @param intent The intent to start.
+ * @param requestCode The code that will be returned with onActivityResult() identifying this
+ * request.
+ * @param options Additional options for how the Activity should be started.
+ * May be null if there are no options. See {@link android.app.ActivityOptions}
+ * for how to build the Bundle supplied here; there are no supported definitions
+ * for building it manually.
+ * @hide
+ */
+ public void startActivityForResult(
+ @NonNull String who, Intent intent, int requestCode, @Nullable Bundle options) {
+ throw new RuntimeException("This method is only implemented for Activity-based Contexts. "
+ + "Check canStartActivityForResult() before calling.");
+ }
+
+ /**
+ * Identifies whether this Context instance will be able to process calls to
+ * {@link #startActivityForResult(String, Intent, int, Bundle)}.
+ * @hide
+ */
+ public boolean canStartActivityForResult() {
+ return false;
+ }
+
+ /**
* Same as {@link #startActivities(Intent[], Bundle)} with no options
* specified.
*
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 8c5a87c..92f0079 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -336,6 +336,17 @@ public class ContextWrapper extends Context {
mBase.startActivityAsUser(intent, user);
}
+ /** @hide **/
+ public void startActivityForResult(
+ String who, Intent intent, int requestCode, Bundle options) {
+ mBase.startActivityForResult(who, intent, requestCode, options);
+ }
+
+ /** @hide **/
+ public boolean canStartActivityForResult() {
+ return mBase.canStartActivityForResult();
+ }
+
@Override
public void startActivity(Intent intent, Bundle options) {
mBase.startActivity(intent, options);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index b6f1e3b..60d2ceb 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -30,6 +30,7 @@ import android.annotation.Nullable;
import android.annotation.Size;
import android.content.ClipData;
import android.content.Context;
+import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -3550,6 +3551,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static SparseArray<String> mAttributeMap;
/**
+ * @hide
+ */
+ String mStartActivityRequestWho;
+
+ /**
* Simple constructor to use when creating a view from code.
*
* @param context The Context the view is running in, through which it can
@@ -4915,6 +4921,58 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Call {@link Context#startActivityForResult(String, Intent, int, Bundle)} for the View's
+ * Context, creating a unique View identifier to retrieve the result.
+ *
+ * @param intent The Intent to be started.
+ * @param requestCode The request code to use.
+ * @hide
+ */
+ public void startActivityForResult(Intent intent, int requestCode) {
+ mStartActivityRequestWho = "@android:view:" + System.identityHashCode(this);
+ getContext().startActivityForResult(mStartActivityRequestWho, intent, requestCode, null);
+ }
+
+ /**
+ * If this View corresponds to the calling who, dispatches the activity result.
+ * @param who The identifier for the targeted View to receive the result.
+ * @param requestCode The integer request code originally supplied to
+ * startActivityForResult(), allowing you to identify who this
+ * result came from.
+ * @param resultCode The integer result code returned by the child activity
+ * through its setResult().
+ * @param data An Intent, which can return result data to the caller
+ * (various data can be attached to Intent "extras").
+ * @return {@code true} if the activity result was dispatched.
+ * @hide
+ */
+ public boolean dispatchActivityResult(
+ String who, int requestCode, int resultCode, Intent data) {
+ if (mStartActivityRequestWho != null && mStartActivityRequestWho.equals(who)) {
+ onActivityResult(requestCode, resultCode, data);
+ mStartActivityRequestWho = null;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Receive the result from a previous call to {@link #startActivityForResult(Intent, int)}.
+ *
+ * @param requestCode The integer request code originally supplied to
+ * startActivityForResult(), allowing you to identify who this
+ * result came from.
+ * @param resultCode The integer result code returned by the child activity
+ * through its setResult().
+ * @param data An Intent, which can return result data to the caller
+ * (various data can be attached to Intent "extras").
+ * @hide
+ */
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ // Do nothing.
+ }
+
+ /**
* Register a callback to be invoked when a hardware key is pressed in this view.
* Key presses in software input methods will generally not trigger the methods of
* this listener.
@@ -13980,6 +14038,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@CallSuper
protected Parcelable onSaveInstanceState() {
mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
+ if (mStartActivityRequestWho != null) {
+ BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE);
+ state.mStartActivityRequestWhoSaved = mStartActivityRequestWho;
+ return state;
+ }
return BaseSavedState.EMPTY_STATE;
}
@@ -14039,13 +14102,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@CallSuper
protected void onRestoreInstanceState(Parcelable state) {
mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
- if (state != BaseSavedState.EMPTY_STATE && state != null) {
+ if (state != null && !(state instanceof AbsSavedState)) {
throw new IllegalArgumentException("Wrong state class, expecting View State but "
+ "received " + state.getClass().toString() + " instead. This usually happens "
+ "when two views of different type have the same id in the same hierarchy. "
+ "This view's id is " + ViewDebug.resolveId(mContext, getId()) + ". Make sure "
+ "other views do not use the same id.");
}
+ if (state != null && state instanceof BaseSavedState) {
+ mStartActivityRequestWho = ((BaseSavedState) state).mStartActivityRequestWhoSaved;
+ }
}
/**
@@ -20735,6 +20801,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* state in {@link android.view.View#onSaveInstanceState()}.
*/
public static class BaseSavedState extends AbsSavedState {
+ String mStartActivityRequestWhoSaved;
+
/**
* Constructor used when reading from a parcel. Reads the state of the superclass.
*
@@ -20742,6 +20810,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public BaseSavedState(Parcel source) {
super(source);
+ mStartActivityRequestWhoSaved = source.readString();
}
/**
@@ -20753,6 +20822,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
super(superState);
}
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeString(mStartActivityRequestWhoSaved);
+ }
+
public static final Parcelable.Creator<BaseSavedState> CREATOR =
new Parcelable.Creator<BaseSavedState>() {
public BaseSavedState createFromParcel(Parcel in) {
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index d0705bb..8d06ce2 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -19,6 +19,7 @@ package android.view;
import android.animation.LayoutTransition;
import android.annotation.IdRes;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.TypedArray;
@@ -814,6 +815,25 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
+ * @hide
+ */
+ @Override
+ public boolean dispatchActivityResult(
+ String who, int requestCode, int resultCode, Intent data) {
+ if (super.dispatchActivityResult(who, requestCode, resultCode, data)) {
+ return true;
+ }
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (child.dispatchActivityResult(who, requestCode, resultCode, data)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
* Find the nearest view in the specified direction that wants to take
* focus.
*
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 5fb0c92..acb92dd 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -27,6 +27,7 @@ import android.content.UndoManager;
import android.content.UndoOperation;
import android.content.UndoOwner;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -109,6 +110,7 @@ import java.text.BreakIterator;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.List;
/**
* Helper class used by TextView to handle editable text views.
@@ -2980,6 +2982,8 @@ public class Editor {
}
}
+ addIntentMenuItemsForTextProcessing(menu);
+
if (menu.hasVisibleItems() || mode.getCustomView() != null) {
mTextView.setHasTransientState(true);
return true;
@@ -3036,6 +3040,32 @@ public class Editor {
styledAttributes.recycle();
}
+ private void addIntentMenuItemsForTextProcessing(Menu menu) {
+ if (mTextView.canProcessText()) {
+ PackageManager packageManager = mTextView.getContext().getPackageManager();
+ List<ResolveInfo> supportedActivities =
+ packageManager.queryIntentActivities(createProcessTextIntent(), 0);
+ for (ResolveInfo info : supportedActivities) {
+ menu.add(info.loadLabel(packageManager))
+ .setIntent(createProcessTextIntentForResolveInfo(info))
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM
+ | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ }
+ }
+ }
+
+ private Intent createProcessTextIntent() {
+ return new Intent()
+ .setAction(Intent.ACTION_PROCESS_TEXT)
+ .setType("text/plain");
+ }
+
+ private Intent createProcessTextIntentForResolveInfo(ResolveInfo info) {
+ return createProcessTextIntent()
+ .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, !mTextView.isTextEditable())
+ .setClassName(info.activityInfo.packageName, info.activityInfo.name);
+ }
+
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
updateReplaceItem(menu);
@@ -3060,6 +3090,13 @@ public class Editor {
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ if (item.getIntent() != null
+ && item.getIntent().getAction().equals(Intent.ACTION_PROCESS_TEXT)) {
+ item.getIntent().putExtra(Intent.EXTRA_PROCESS_TEXT, mTextView.getSelectedText());
+ mTextView.startActivityForResult(
+ item.getIntent(), TextView.PROCESS_TEXT_REQUEST_CODE);
+ return true;
+ }
if (mCustomSelectionActionModeCallback != null &&
mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
return true;
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 11439e4..5d60f99 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -27,6 +27,7 @@ import android.annotation.XmlRes;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
+import android.content.Intent;
import android.content.UndoManager;
import android.content.res.ColorStateList;
import android.content.res.CompatibilityInfo;
@@ -289,6 +290,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// System wide time for last cut or copy action.
static long LAST_CUT_OR_COPY_TIME;
+ /**
+ * @hide
+ */
+ static final int PROCESS_TEXT_REQUEST_CODE = 100;
+
private ColorStateList mTextColor;
private ColorStateList mHintTextColor;
private ColorStateList mLinkTextColor;
@@ -1414,6 +1420,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ /**
+ * @hide
+ */
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
+ CharSequence result = data != null
+ ? data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT)
+ : "";
+ if (isTextEditable()) {
+ replaceSelectionWithText(result);
+ } else {
+ Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+
private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
Typeface tf = null;
if (familyName != null) {
@@ -7474,6 +7497,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return selectionStart >= 0 && selectionStart != selectionEnd;
}
+ String getSelectedText() {
+ if (hasSelection()) {
+ return String.valueOf(mText.subSequence(getSelectionStart(), getSelectionEnd()));
+ }
+ return null;
+ }
+
/**
* Sets the properties of this field (lines, horizontally scrolling,
* transformation method) to be for a single-line input.
@@ -9066,6 +9096,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
hasPrimaryClip());
}
+ boolean canProcessText() {
+ if (!getContext().canStartActivityForResult() || getId() == View.NO_ID
+ || hasPasswordTransformationMethod()) {
+ return false;
+ }
+
+ if (mText.length() > 0 && hasSelection() && mEditor != null) {
+ return true;
+ }
+
+ return false;
+ }
+
boolean selectAllText() {
// Need to hide insert point cursor controller before settings selection, otherwise insert
// point cursor controller obtains cursor update event and update cursor with cancelling
@@ -9078,6 +9121,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return length > 0;
}
+ void replaceSelectionWithText(CharSequence text) {
+ ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
+ }
+
/**
* Paste clipboard content between min and max positions.
*/