Skip to content

WIP: Search in Card Browser for highlighted text from Review mode#4708

Merged
timrae merged 5 commits intoankidroid:masterfrom
Profpatsch:search-from-review
Oct 6, 2017
Merged

WIP: Search in Card Browser for highlighted text from Review mode#4708
timrae merged 5 commits intoankidroid:masterfrom
Profpatsch:search-from-review

Conversation

@Profpatsch
Copy link
Copy Markdown
Contributor

@Profpatsch Profpatsch commented Sep 20, 2017

I tried my hands at #4496, but that’s as far as I can get. See commit message for the problem.
Basically I can’t find a way to add the callbacks to the action mode. Also, I have no idea what action mode I’m looking at.

I thought about tracking for long presses, but then the question becomes how to still get the text selection functionality.

@timrae: Maybe you have an idea how to implement this functionality?

@timrae
Copy link
Copy Markdown
Member

timrae commented Sep 20, 2017

@Profpatsch

Thanks for having a go at this. It took me a while to find the article, but this is the way that I was imagining it should be implemented in my comment on #4496. By default, that would add an item to the floating action bar whenever text is selected inside any app, which may not be what users want. But according to the comments section of that article, an issue was added and subsequently fixed that would allow you to limit the visibility of the intents to your own app if you set exported to false, which I think we should do.

@timrae
Copy link
Copy Markdown
Member

timrae commented Sep 20, 2017

FYI, this may also be helpful, though I'm not sure how up-to-date it is.

@Profpatsch
Copy link
Copy Markdown
Contributor Author

Okay, I rewrote it to a very simple implementation based on ACTION_PROCESS_TEXT.

It’s still visible from other apps because the hiding functionality is not in Android yet. I also don’t know if I have implemented it in a backwards-compatible manner, since I only have a new device to test it on.

Copy link
Copy Markdown
Member

@timrae timrae left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACTION_PROCESS_TEXT is only available from API 23, so you'll need to move any code that uses it to our Compat classes.

@timrae
Copy link
Copy Markdown
Member

timrae commented Sep 28, 2017

Normally we just test with the simulator during development

@timrae
Copy link
Copy Markdown
Member

timrae commented Sep 28, 2017

setHandleNativeActionModesEnabled is available on 4.0 and above. I think we're finally ready to bump the minimum supported version up to 4.1 for v2.9, but we'd better announce it on the forums.

@Profpatsch
Copy link
Copy Markdown
Contributor Author

Okay, I moved the ACTION_PROCESS_TEXT-specific logic into a Compat method.

@timrae
Copy link
Copy Markdown
Member

timrae commented Sep 28, 2017

Almost, but that's not quite going to work; you need to instantiate the correct Compat class for the correct API using CompatHelper:

https://github.com/ankidroid/Anki-Android/blob/master/AnkiDroid/src/main/java/com/ichi2/compat/CompatHelper.java

@Profpatsch
Copy link
Copy Markdown
Contributor Author

Profpatsch commented Sep 28, 2017

Right. Added v23 to the dispatcher and switched to the singleton method.

@Profpatsch
Copy link
Copy Markdown
Contributor Author

@timrae Any further remarks?

@timrae
Copy link
Copy Markdown
Member

timrae commented Oct 3, 2017 via email

@timrae
Copy link
Copy Markdown
Member

timrae commented Oct 3, 2017

I've made the announcement that Android 4.0 will be the new minimum going forward. We can bump the minimum again to 4.1 whenever the need arises.

@Profpatsch
Copy link
Copy Markdown
Contributor Author

Profpatsch commented Oct 4, 2017

I'm also still unsure about this exported issue...
When was support for this added into the Android framework? One of us will
need to check the framework code.

I’m actually not sure if it wouldn’t be better to export it. That way, if you find e.g. a word in your browser, you can highlight it and directly see if you already have it in your vocab list, which is awesome.
The item text uses the Activity name, so it might be sensible to rename it to “Vocab Browser” for example?

Update: Right now the browser crashes once I click “Card Browser” though, probably a SecurityException?

@timrae
Copy link
Copy Markdown
Member

timrae commented Oct 4, 2017

I’m actually not sure if it wouldn’t be better to export it. That way, if you find e.g. a word in your browser, you can highlight it and directly see if you already have it in your vocab list, which is awesome.

This sounds like it could be useful if we could streamline the process of note addition from the browser a bit better, so that when you choose "Add note" from the browser while there is something in the search box, it automatically populates that text into the first field of the note editor.

@timrae
Copy link
Copy Markdown
Member

timrae commented Oct 4, 2017

The item text uses the Activity name, so it might be sensible to rename it to “Vocab Browser” for example?

There's no way to customize it without changing the Activity name?

Update: Right now the browser crashes once I click “Card Browser” though, probably a SecurityException?

What's the stacktrace?

@Profpatsch
Copy link
Copy Markdown
Contributor Author

There's no way to customize it without changing the Activity name?

At least that’s what the article says:
screenshot

I can’t find any documentation other than https://developer.android.com/reference/android/content/Intent.html#ACTION_PROCESS_TEXT and that medium article; apparently Medium is the way the Android team distributes their docs?

What's the stacktrace?

10-04 02:57:31.336  2182 22848 I ActivityManager: START u0 {act=android.intent.action.PROCESS_TEXT typ=text/plain cmp=com.ichi2.anki/.CardBrowser (has extras)} from uid 10050 on display 0
10-04 02:57:31.336  2182 22848 W ActivityManager: Permission Denial: starting Intent { act=android.intent.action.PROCESS_TEXT typ=text/plain cmp=com.ichi2.anki/.CardBrowser (has extras) } from ProcessRecord{5ad81a9 26760:org.lineageos.jelly/u0a50} (pid=26760, uid=10050) not exported from uid 10069
10-04 02:57:31.339 26760 26760 D AndroidRuntime: Shutting down VM
10-04 02:57:31.340 26760 26760 E AndroidRuntime: FATAL EXCEPTION: main
10-04 02:57:31.340 26760 26760 E AndroidRuntime: Process: org.lineageos.jelly, PID: 26760
10-04 02:57:31.340 26760 26760 E AndroidRuntime: java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.PROCESS_TEXT typ=text/plain cmp=com.ichi2.anki/.CardBrowser (has extras) } from ProcessRecord{5ad81a9 26760:org.lineageos.jelly/u0a50} (pid=26760, uid=10050) not exported from uid 10069
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at android.os.Parcel.readException(Parcel.java:1684)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at android.os.Parcel.readException(Parcel.java:1637)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at android.app.ActivityManagerProxy.startActivity(ActivityManagerNative.java:3101)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at android.app.Instrumentation.execStartActivity(Instrumentation.java:1641)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at android.app.Activity.startActivityForResult(Activity.java:4879)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at android.content.ContextWrapper.startActivityForResult(ContextWrapper.java:368)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at android.view.View.startActivityForResult(View.java:5845)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at android.widget.Editor$ProcessTextIntentActionsHandler.fireIntent(Editor.java:6246)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at android.widget.Editor$ProcessTextIntentActionsHandler.performMenuItemAction(Editor.java:6200)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at android.widget.Editor$TextActionModeCallback.onActionItemClicked(Editor.java:3786)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at com.android.internal.policy.DecorView$ActionModeCallback2Wrapper.onActionItemClicked(DecorView.java:2303)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at com.android.internal.view.FloatingActionMode$3.onMenuItemSelected(FloatingActionMode.java:88)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at com.android.internal.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:761)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at com.android.internal.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:152)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at com.android.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:904)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at com.android.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:894)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at com.android.internal.view.FloatingActionMode$4.onMenuItemClick(FloatingActionMode.java:114)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at com.android.internal.widget.FloatingToolbar$FloatingToolbarPopup$15.onItemClick(FloatingToolbar.java:1379)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at android.widget.AdapterView.performItemClick(AdapterView.java:310)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at android.widget.AbsListView.performItemClick(AbsListView.java:1164)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at android.widget.AbsListView$PerformClick.run(AbsListView.java:3139)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at android.widget.AbsListView$3.run(AbsListView.java:4054)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at android.os.Handler.handleCallback(Handler.java:751)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at android.os.Handler.dispatchMessage(Handler.java:95)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at android.os.Looper.loop(Looper.java:154)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at android.app.ActivityThread.main(ActivityThread.java:6186)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at java.lang.reflect.Method.invoke(Native Method)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
10-04 02:57:31.340 26760 26760 E AndroidRuntime: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)
10-04 02:57:31.343  2182 22597 W ActivityManager:   Force finishing activity org.lineageos.jelly/.MainActivity

@timrae
Copy link
Copy Markdown
Member

timrae commented Oct 4, 2017

Hmmm, this stack trace was generated with exported=true?

Permission Denial: starting Intent { act=android.intent.action.PROCESS_TEXT typ=text/plain cmp=com.ichi2.anki/.CardBrowser (has extras) } from ProcessRecord{5ad81a9 26760:org.lineageos.jelly/u0a50} (pid=26760, uid=10050) not exported from uid 10069

@timrae
Copy link
Copy Markdown
Member

timrae commented Oct 4, 2017

I'm a bit confused, as I would have thought that on older versions of Android the PROCESS_TEXT intent is simply never sent...

@Profpatsch
Copy link
Copy Markdown
Contributor Author

Hmmm, this stack trace was generated with exported=true?

I understand now, I should have changed the exported attribute to true. Once I did, it works.

Calling an `ACTION_PROCESS_TEXT` intent from other applications was still
possible, but they would crash with a `SecurityException` because the Activity
was not exported.
@timrae
Copy link
Copy Markdown
Member

timrae commented Oct 4, 2017

Instead of that wrapper function inside the Compat classes, I think it might be more simple to just port over the missing constants from the framework.

public class CompatV10 implements Compat {
    public static final String ACTION_PROCESS_TEXT = "android.intent.action.PROCESS_TEXT";
    public static final String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT";
    // ....
}

public class CompatV23 implements Compat {
    public static final String ACTION_PROCESS_TEXT = Intent.ACTION_PROCESS_TEXT;
    public static final String EXTRA_PROCESS_TEXT = Intent.EXTRA_PROCESS_TEXT;
    // ...
}

Then you can write the code more naturally inside the reviewer using getCompat().ACTION_PROCESS_TEXT for the constant.

@timrae
Copy link
Copy Markdown
Member

timrae commented Oct 4, 2017

I understand now, I should have changed the exported attribute to true. Once I did, it works.

Ah right, yes, the activity itself...

@Profpatsch
Copy link
Copy Markdown
Contributor Author

Instead of that wrapper function inside the Compat classes, I think it might be more simple to just port over the missing constants from the framework.

Ah right, yes, the activity itself...

So no need to copy the strings, right?

@timrae
Copy link
Copy Markdown
Member

timrae commented Oct 4, 2017

So no need to copy the strings, right?

Sorry I'm not following your question... Did you see the code sample I posted above?

@Profpatsch
Copy link
Copy Markdown
Contributor Author

Sorry I'm not following your question... Did you see the code sample I posted above?

Yeah, but I’m not quite sure what this would simplify and how. Do you mean I should remove this function alltogether?

@timrae
Copy link
Copy Markdown
Member

timrae commented Oct 4, 2017

Yes, the only part of the function that is not available on all APIs is the two constants, right?

@Profpatsch
Copy link
Copy Markdown
Contributor Author

Ah! Now I get it. Should have gone to bed earlier yesterday. :)
Will do.

@Profpatsch
Copy link
Copy Markdown
Contributor Author

Profpatsch commented Oct 4, 2017

I implemented it; the final strings have to be in the interface, otherwise they cannot be used with getCompat() (which returns the interface). I refuse to cast and break the abstraction. ;)

Tested with KitKat Emulator and on API 23 device.

Compat compat = CompatHelper.getCompat();
if (intent.getAction() == compat.ACTION_PROCESS_TEXT) {
CharSequence search = intent.getCharSequenceExtra(compat.EXTRA_PROCESS_TEXT);
assert search != null;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this is much clearer! I think we don't really want to crash if an app developer accidentally doesn't add EXTRA_PROCESS_TEXT... Can you get rid of the assert and just write the following?

if (search != null && search.length() != 0) {

Last nitpick I promise :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't really want to crash if an app developer accidentally doesn't add EXTRA_PROCESS_TEXT

That’s something that should never happen, which is why I use an assert.
From the ACTION_PROCESS_TEXT documentation:

Activity Action: Process a piece of text.
Input: EXTRA_PROCESS_TEXT contains the text to be processed. EXTRA_PROCESS_TEXT_READONLY states if the resulting text will be read-only.
Output: EXTRA_PROCESS_TEXT contains the processed text.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the ACTION_PROCESS_TEXT documentation

It doesn't actually say anywhere there that it won't be null, or that it's guaranteed to be present. In fact it will only be there if the developer sending the intent calls putExtra(). You can see this from the getCharSequenceExtra() documentation, which says:

(returns) the value of an item that previously added with putExtra() or null if no CharSequence value was found.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: In general, we try to only use assert() to pick up programming errors by us, whereas this would be picking up programming errors by others (i.e. other apps, android framework).

import com.ichi2.async.DeckTask.TaskData;
import com.ichi2.compat.Compat;
import com.ichi2.compat.CompatHelper;
import com.ichi2.compat.CompatV23;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I guess this import isn't needed anymore btw

import android.annotation.TargetApi;
import android.content.Intent;

import timber.log.Timber;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also not needed

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compiler doesn’t show these as warnings … :(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, most compilers don't unfortunately, even in other programming languages...You can run "optimize imports" with Android Studio which will take care of this for you automatically.

@Profpatsch
Copy link
Copy Markdown
Contributor Author

Profpatsch commented Oct 6, 2017

Unneeded imports & assert have been fixed.

Want to say that I’m impressed by the amount attention and help you have given for this rather minor feature, @timrae.

if (intent.getAction() == compat.ACTION_PROCESS_TEXT) {
CharSequence search = intent.getCharSequenceExtra(compat.EXTRA_PROCESS_TEXT);
if (search != null && search.length() != 0) {
Timber.d("CardBrowser :: Called with search intent", search);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you forgot to add the %s formatting string here so that the content of search gets printed out...?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, it’s a format string. Wasn’t clear from the documentation, thought it would display them automatically in a smart way.

It is enough to mock the Intent string constants for older API versions, this
way the logic stays at the correct place.
@timrae timrae merged commit ce0404d into ankidroid:master Oct 6, 2017
@timrae
Copy link
Copy Markdown
Member

timrae commented Oct 6, 2017

Thank you! :)

@timrae
Copy link
Copy Markdown
Member

timrae commented Oct 6, 2017

Are you interested in implementing the idea I posted above to allow adding a new note from the content of the browser search bar?

@Profpatsch
Copy link
Copy Markdown
Contributor Author

Profpatsch commented Nov 3, 2017

Are you interested in implementing the idea I posted above to allow adding a new note from the content of the browser search bar?

At the moment I’m short on time, but I’ll put it on my side-project list. (The tab with this question has been open in my browser for nearly a month now. :))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants