Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

apiVersioning and developerContact implementation for AnkiDroid functions call from WebView #6521

Merged
merged 25 commits into from
Aug 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
bc30c1d
Revert "Fork Update"
krmanik Jun 25, 2020
6a3339c
Update fork
krmanik Jun 26, 2020
c8181c3
Merge branch 'master' of https://github.com/ankidroid/Anki-Android
krmanik Jun 28, 2020
0d37a81
Merge branch 'master' of https://github.com/ankidroid/Anki-Android
krmanik Jun 30, 2020
6184bef
api versioning
krmanik Jun 21, 2020
7254e0b
Updated AbstractFlashcardViewer.java, 02-strings.xml
krmanik Jun 24, 2020
939e6c1
Added Vesrion Compare using Comparable
krmanik Jun 27, 2020
638512e
Added Java SemVer for JS API
krmanik Jun 28, 2020
ef8cdcb
Updated AbstractFlashcardViewer.java, 02-strings.xml
krmanik Jul 1, 2020
474b616
Updated 02-strings.xml
krmanik Jul 4, 2020
d8b7c41
Updated 02-strings.xml
krmanik Jul 4, 2020
6544a1e
Updated 02-strings.xml
krmanik Jul 4, 2020
e3571aa
Merge branch 'master' into js-api-version
krmanik Jul 4, 2020
75dc239
Updated 02-strings.xml
krmanik Jul 5, 2020
e68e5a5
Updated AbstractFlashcardViewer.java
krmanik Jul 5, 2020
fe482d7
Updated AbstractFlashcardViewer.java
krmanik Jul 5, 2020
80079d9
Handle when API not initialised
krmanik Jul 5, 2020
e2f068a
Update JS API
krmanik Jul 25, 2020
401cddc
Update js api
krmanik Jul 26, 2020
1399b90
Merge branch 'master' of https://github.com/ankidroid/Anki-Android in…
krmanik Aug 4, 2020
7204912
Update 02-strings.xml
krmanik Aug 4, 2020
793063e
Update AbstractFlashcardViewer.java
krmanik Aug 4, 2020
c055f95
init js api
krmanik Aug 5, 2020
6a2b548
Updated
krmanik Aug 6, 2020
c0ff6c3
Updated
krmanik Aug 7, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions AnkiDroid/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,7 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'org.smali:dexlib2:2.4.0'

//For AnkiDroid JS API Versioning
implementation "com.github.zafarkhaja:java-semver:0.9.0"
}
160 changes: 159 additions & 1 deletion AnkiDroid/src/main/java/com/ichi2/anki/AbstractFlashcardViewer.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@

import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.materialdialogs.util.TypefaceHelper;
import com.google.android.material.snackbar.Snackbar;
import com.google.gson.Gson;
import com.ichi2.anim.ActivityTransitionAnimation;
import com.ichi2.anim.ViewAnimation;
import com.ichi2.anki.dialogs.TagsDialog;
Expand Down Expand Up @@ -129,6 +131,7 @@
import java.lang.ref.WeakReference;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
Expand All @@ -146,6 +149,8 @@
import static com.ichi2.async.CollectionTask.TASK_TYPE.*;
import com.ichi2.async.TaskData;

import com.github.zafarkhaja.semver.Version;

@SuppressWarnings({"PMD.AvoidThrowingRawExceptionTypes","PMD.FieldDeclarationsShouldBeAtStartOfClass"})
public abstract class AbstractFlashcardViewer extends NavigationDrawerActivity implements ReviewerUi, CommandProcessor {

Expand Down Expand Up @@ -194,6 +199,21 @@ public abstract class AbstractFlashcardViewer extends NavigationDrawerActivity i
// ETA
private int eta;

// js api developer contact
private String mCardSuppliedDeveloperContact = "";
private String mCardSuppliedApiVersion = "";

private static final String sCurrentJsApiVersion = "0.0.1";
private static final String sMinimumJsApiVersion = "0.0.1";

// JS API ERROR CODE
private static final int ankiJsErrorCodeDefault = 0;
private static final int ankiJsErrorCodeMarkCard = 1;
private static final int ankiJsErrorCodeFlagCard = 2;

// JS api list enable/disable status
private HashMap<String, Boolean> mJsApiListMap = new HashMap<String, Boolean>();
david-allison marked this conversation as resolved.
Show resolved Hide resolved

private boolean isInFullscreen;

/**
Expand Down Expand Up @@ -1928,6 +1948,9 @@ public void onChronometerTick(Chronometer chronometer) {

protected void displayCardQuestion() {
displayCardQuestion(false);

// js api initialisation / reset
jsApiInit();
}

protected void displayCardQuestion(boolean reload) {
Expand Down Expand Up @@ -3228,11 +3251,28 @@ private boolean filterUrl(String url) {
}
// mark card using javascript
if (url.startsWith("signal:mark_current_card")) {
executeCommand(COMMAND_MARK);
if (isAnkiApiNull("markCard")) {
showDeveloperContact(ankiJsErrorCodeDefault);
return true;
} else if (mJsApiListMap.get("markCard")) {
david-allison marked this conversation as resolved.
Show resolved Hide resolved
executeCommand(COMMAND_MARK);
} else {
// see 02-string.xml
showDeveloperContact(ankiJsErrorCodeMarkCard);
}
return true;
}
// flag card (blue, green, orange, red) using javascript from AnkiDroid webview
if (url.startsWith("signal:flag_")) {
if (isAnkiApiNull("toggleFlag")) {
showDeveloperContact(ankiJsErrorCodeDefault);
return true;
} else if (!mJsApiListMap.get("toggleFlag")) {
// see 02-string.xml
showDeveloperContact(ankiJsErrorCodeFlagCard);
return true;
}

String mFlag = url.replaceFirst("signal:flag_","");
switch (mFlag) {
case "none": executeCommand(COMMAND_UNSET_FLAG);
Expand Down Expand Up @@ -3489,6 +3529,78 @@ void handleUrlFromJavascript(String url) {
}
}

// Check if value null
private boolean isAnkiApiNull(String api) {
return mJsApiListMap.get(api) == null;
}

/*
* see 02-strings.xml
* Show Error code when mark card or flag card unsupported
* 1 - mark card
* 2 - flag card
*
* show developer contact if js api used in card is deprecated
*/
private void showDeveloperContact(int errorCode) {
david-allison marked this conversation as resolved.
Show resolved Hide resolved
String errorMsg = getString(R.string.anki_js_error_code, errorCode);

View parentLayout = findViewById(android.R.id.content);
String snackbarMsg;
snackbarMsg = getString(R.string.api_version_developer_contact, mCardSuppliedDeveloperContact, errorMsg);

Snackbar snackbar = Snackbar.make(parentLayout, snackbarMsg, Snackbar.LENGTH_LONG);
View snackbarView = snackbar.getView();
TextView snackTextView = snackbarView.findViewById(com.google.android.material.R.id.snackbar_text);
snackTextView.setTextColor(Color.WHITE);
snackTextView.setMaxLines(3);

snackbar.setActionTextColor(Color.MAGENTA)
.setAction(getString(R.string.reviewer_invalid_api_version_visit_documentation), view -> {
openUrl(Uri.parse("https://github.com/ankidroid/Anki-Android/wiki"));
});

snackbar.show();
}

/**
* Supplied api version must be equal to current api version to call mark card, toggle flag functions etc.
*/
private boolean requireApiVersion(String apiVer, String apiDevContact) {
try {

if (TextUtils.isEmpty(apiDevContact)) {
return false;
}

Version mVersionCurrent = Version.valueOf(sCurrentJsApiVersion);
david-allison marked this conversation as resolved.
Show resolved Hide resolved
Version mVersionSupplied = Version.valueOf(apiVer);

/*
* if api major version equals to supplied major version then return true and also check for minor version and patch version
* show toast for update and contact developer if need updates
* otherwise return false
*/
if (mVersionSupplied.equals(mVersionCurrent)) {
return true;
david-allison marked this conversation as resolved.
Show resolved Hide resolved
} else if (mVersionSupplied.lessThan(mVersionCurrent)) {
UIUtils.showThemedToast(AbstractFlashcardViewer.this, getString(R.string.update_js_api_version, mCardSuppliedDeveloperContact), false);

if (mVersionSupplied.greaterThanOrEqualTo(Version.valueOf(sMinimumJsApiVersion))) {
return true;
} else {
return false;
}
} else {
UIUtils.showThemedToast(AbstractFlashcardViewer.this, getString(R.string.valid_js_api_version, mCardSuppliedDeveloperContact), false);
return false;
}
} catch (Exception e) {
Timber.w(e, "requireApiVersion::exception");
}
return false;
}

@VisibleForTesting
void loadInitialCard() {
CollectionTask.launchCollectionTask(ANSWER_CARD, mAnswerCardHandler(false),
Expand Down Expand Up @@ -3531,12 +3643,58 @@ protected void showTagsDialog() {
showDialogFragment(dialog);
}

// init or reset api list
private void jsApiInit() {
mCardSuppliedApiVersion = "";
mCardSuppliedDeveloperContact = "";

for (int i = 0; i < mApiList.length; i++) {
mJsApiListMap.put(mApiList[i], false);
}
}

/*
Javascript Interface class for calling Java function from AnkiDroid WebView
see card.js for available functions
*/
// list of api that can be accessed
private final String[] mApiList = {"toggleFlag", "markCard"};

public class JavaScriptFunction {

private final Gson mGson = new Gson();
mikehardy marked this conversation as resolved.
Show resolved Hide resolved

// if supplied api version match then enable api
private void enableJsApi() {
for (int i = 0; i < mApiList.length; i++) {
mJsApiListMap.put(mApiList[i], true);
}
}

@JavascriptInterface
public String init(String jsonData) {
JSONObject data;
String apiStatusJson = "";

try {
data = new JSONObject(jsonData);
if (!(data == JSONObject.NULL)) {
mCardSuppliedApiVersion = data.optString("version", "");
mCardSuppliedDeveloperContact = data.optString("developer", "");

if (requireApiVersion(mCardSuppliedApiVersion, mCardSuppliedDeveloperContact)) {
enableJsApi();
}

apiStatusJson = mGson.toJson(mJsApiListMap);
}

} catch (JSONException j) {
UIUtils.showThemedToast(AbstractFlashcardViewer.this, getString(R.string.invalid_json_data, j.getLocalizedMessage()), false);
}
return String.valueOf(apiStatusJson);
}

@JavascriptInterface
public String ankiGetNewCardCount() {
return newCount.toString();
Expand Down
10 changes: 9 additions & 1 deletion AnkiDroid/src/main/res/values/02-strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -306,4 +306,12 @@
<item quantity="one">%d note unchanged</item>
<item quantity="other">%d notes unchanged</item>
</plurals>
</resources>

<!-- JS api -->
<string name="api_version_developer_contact">This card uses unsupported AnkiDroid features. Contact developer %s, or view the wiki. %s</string>
<string name="invalid_json_data">Card provided invalid data. %s</string>
<string name="valid_js_api_version">Invalid AnkiDroid JS API version. Contact developer %s, or view wiki</string>
<string name="update_js_api_version">AnkiDroid JS API update available. Contact developer %s, or view wiki</string>
<string name="reviewer_invalid_api_version_visit_documentation">View</string>
<string name="anki_js_error_code">(Error Code: %d)</string>
</resources>