Book Excerpt: ACTION_PROCESS_TEXT Security

The following sections are excerpts from Version 6.9 of “The Busy Coder’s Guide to Android Development”, with slight modifications to fit the blog format. They are from the same chapter that introduced ACTION_PROCESS_TEXT itself.

Limitations of ACTION_PROCESS_TEXT

ACTION_PROCESS_TEXT is “not all unicorns and rainbows”. There are a few issues that you will need to take into account.

Security

There is no documented android:permission attribute to place on the <activity> that is offering ACTION_PROCESS_TEXT, to limit callers. Ideally, we could limit invocations of ACTION_PROCESS_TEXT only to the firmware itself. As it stands, any app can call startActivity() (or, worse, startActivityForResult()) for your ACTION_PROCESS_TEXT activity and have your code process the text (with user intervention). Please be sure that if you return data via EXTRA_PROCESS_TEXT that the data not include any private information or anything that needs to be secured.

With luck, this will be improved in a future version of Android.

Landscape

In Android 6.0, the text-selection floating action mode for an EditText does not appear in landscape mode, when the entire UI is dedicated to the text-entry screen with the input method editor. With luck, this too will be fixed in a future version of Android.

Blocking ACTION_PROCESS_TEXT

There will be cases where you do not want ACTION_PROCESS_TEXT to be offered to your users. For example, perhaps the text contains sensitive information that should not be passed outside of your app.

The best solution, particularly for a TextView, is to mark the text as not being selectable. This is accomplished via android:textIsSelectable="false" in a layout file, or via setTextIsSelectable(false) in Java. false is the default value for TextView.

However, for an EditText widget, true is the default is-selectable state, and you cannot seem to override that with setTextIsSelectable(false).

There is no officially supported option for handling this case, though perhaps there will be one in the future.

One unsupported hack of a workaround relies upon the fact that EditText blocks the floating action mode for password fields. In the source code to EditText, TextView, and related classes, this is handled by seeing if the TransformationMethod associated with the widget is PasswordTransformationMethod. A TransformationMethod is responsible for on-the-fly adjustments between what the user types and what the user sees, such as PasswordTransformationMethod replacing typed-in characters with dots.

Making your EditText widget use PasswordTransformationMethod itself is fine for actual password fields. But suppose you have an EditText whose contents should be kept private but should not have the input-shrouding effect of PasswordTransformationMethod. To offer this, you would need to create a subclass of PasswordTransformationMethod (so the block-the-floating-action-mode logic works) that does not actually transform the text (to block the changes that PasswordTransformationMethod would ordinarily apply).

A proof-of-concept implementation of this can be found in the Introspection/ProcessTextBlocker sample application. This is a clone of the FilesEditor sample app from the book’s chapter on file I/O, where there are a series of tabs in a ViewPager, with a large EditText widget in each tab, managed by an EditorFragment. This version of the sample implements one change: the use of DummyTransformationMethod:

private static class DummyTransformationMethod
  extends PasswordTransformationMethod {
  @Override
  public CharSequence getTransformation(CharSequence source,
                                        View view) {
    return(source);
  }

  @Override
  public void onTextChanged(CharSequence s, int start,
                            int before, int count) {
    // no-op
  }

  @Override
  public void onFocusChanged(View view,
                             CharSequence sourceText,
                             boolean focused, int direction,
                             Rect previouslyFocusedRect) {
    // no-op
  }

  @Override
  public void afterTextChanged(Editable s) {
    // no-op
  }

  @Override
  public void beforeTextChanged(CharSequence s, int start,
                                int count, int after) {
    // no-op
  }
}

This is a do-nothing TransformationMethod. Ordinarily, this would be completely useless. However, it inherits from PasswordTransformationMethod, which is what we need to block the floating action mode.

In onCreateView() of the EditorFragment, we apply a DummyTransformationMethod via setTransformationMethod():

@Override
public View onCreateView(LayoutInflater inflater,
                         ViewGroup container,
                         Bundle savedInstanceState) {
  View result=inflater.inflate(R.layout.editor, container, false);

  editor=(EditText)result.findViewById(R.id.editor);
  editor.setTransformationMethod(new DummyTransformationMethod());

  return(result);
}

However, this approach has limitations:

  • It only works in portrait, not landscape, for unclear reasons. Since ACTION_PROCESS_TEXT also only works in portrait, not landscape, we still succeed in blocking ACTION_PROCESS_TEXT options.

  • It blocks the entire floating action mode (in portrait), clobbering the existing cut/copy/paste/select-all options that might ordinarily be there.

  • Since it is tied to internal implementation (that the floating action mode is suppressed when using an instanceof a PasswordTransformationMethod), not only is this subject to change across Android versions, but also it is subject to change based on device manufacturer or custom ROM tweaks to the Android source code.