Skip to content

Commit

Permalink
Merge pull request #11008 from keymanapp/chore/beta-to-master-b17s3
Browse files Browse the repository at this point in the history
chore: Merge beta to master for Sprint B17S3
  • Loading branch information
darcywong00 authored Mar 16, 2024
2 parents e8d0737 + 39b56b3 commit 62b3e3b
Show file tree
Hide file tree
Showing 277 changed files with 14,056 additions and 1,372 deletions.
70 changes: 70 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,76 @@
* chore(common): move to 18.0 alpha (#10713)
* chore: move to 18.0 alpha

## 17.0.288 beta 2024-03-13

* test(developer): kmc keyboard info compiler messages unit tests (#10848)
* fix(developer): getFontFamily now returns null if ttfMeta.promise errors (#10983)
* docs(android/app): Add data privacy policy page (#10985)
* fix(developer): min Keyman version for LDML is 17.0 (#10977)

## 17.0.287 beta 2024-03-12

* chore(developer): resolve excessive-fatal-exception issue (#10949)
* fix(linux): Remove `ibus-keyman.post{inst,rm}` and verify that `ibus-daemon` is running when we start km-config (#10788)

## 17.0.286 beta 2024-03-11

* chore(web): improve the clarity of hints on OSK for Android (#10971)

## 17.0.285 beta 2024-03-08

* fix(windows): GetKeyboardLanguage exits early on an invalid keyboard ID (#10936)

## 17.0.284 beta 2024-03-07

* fix(web): globe key highlighting (#10932)
* chore(web): updates mtnt model used by test page (#10935)
* fix(developer): kmc keyboard info compiler no kmp error (#10893)
* fix(developer): Treat Right Shift as Shift in debugger (#10919)
* docs(developer): kmc-model api documentation (#10921)
* docs(developer): kmc-model-info api documentation (#10922)
* docs(developer): kmc-keyboard-info api documentation (#10923)
* docs(developer): kmc-package api documentation (#10924)
* fix(developer): normalize all input paths in .kps compiler (#10950)

## 17.0.283 beta 2024-03-06

* fix(android): fixes context-change detection for repeated-char cases (#10873)
* fix(developer): search-term quote replacement was not global (#10934)
* (#10913)
* fix(developer): fix for errant \uXXXX error (#10946)
* (#10948)
* refactor(oem/fv/android): Install fallback keyboard (#10907)
* refactor(android/app): Move storage permission checks (#10904)
* fix(common): missing script exec bit (#10951)

## 17.0.282 beta 2024-03-05

* fix(developer): move osk.ts from developer-utils to kmc-kmn (#10903)
* fix(developer): hint text special characters (#10927)
* docs(developer): kmc-ldml api documentation (#10917)
* chore(developer): consolidate external links in Developer messages (#10918)
* fix(developer): message export filename was title case (#10941)

## 17.0.281 beta 2024-03-04

* chore(linux): Update debian changelog (#10897)
* fix(ios): cross-paragraph keyboard rules (#10905)
* chore(developer): deploy compiler messages to help site (#10906)

## 17.0.280 beta 2024-03-01

* fix(developer): make sure scroll bar appears when needed in global welcome (#10818)
* chore(common): loosen .keyman-touch-layout schema layer id requirements (#10819)
* docs(developer): kmc-kmn api documentation and some message documentation (#10856)
* refactor(developer): cleanup kmn compiler message namespaces (#10867)
* refactor(developer): reorganize messages for adding details (#10878)
* fix(ios): context-initial diacritic handling (#10589)
* feat(developer): adds kmc message command (#10888)
* test(developer): kmc unit test infrastructure messages (#10825)
* fix(android): handle surrogate pairs in selection range indexing (#10885)
* feat(windows): move the code kmshell.dpr to unit so that only the includes are in dpr (#10871)

## 17.0.279 beta 2024-02-29

* fix(linux): Fix libkeymancore-dev dependencies (#10880)
Expand Down
6 changes: 5 additions & 1 deletion android/KMAPro/kMAPro/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
<!-- Keyman Engine removes INTERNET permission, so add it back -->
<uses-permission android:name="android.permission.INTERNET" tools:node="replace" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.VIBRATE" />

<!-- Devices running up to Android 12L
Starting in API level 33, this permission has no effect:
https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

<!-- Ideally we would declare this permission so we can get other installed keyboard names.
But would involve a long review process with the Play Store:
https://support.google.com/googleplay/android-developer/answer/9214102#
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* Copyright 2024 SIL International
*/
package com.tavultesoft.kmapro;

import static com.tavultesoft.kmapro.MainActivity.PERMISSION_REQUEST_STORAGE;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.widget.Toast;

import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

public class CheckPermissions {

public static boolean isPermissionOK(Activity activity) {
boolean permissionsOK = true;

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return permissionsOK;
}

if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
permissionsOK = Environment.isExternalStorageManager();
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.TIRAMISU) {
// TODO: Workout scoped storage permission #10659
}
} else {
permissionsOK = checkPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
}

return permissionsOK;
}

public static boolean checkPermission(Activity activity, String permission) {
return ContextCompat.checkSelfPermission(activity.getApplicationContext(), permission) == PackageManager.PERMISSION_GRANTED;
}

public static void requestPermission (Activity activity, Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
// Need to request multiple permissions at once
// TODO: request multiple permissions at once #10659
showPermissionDenied(context);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// TODO: find alternative to below. #10659
// It works, but would need Play Store approval
// For now, show permission denied
showPermissionDenied(context);
/*
try {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
intent.setData(uri);
activity.startActivity(intent);
} catch (Exception e) {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
activity.startActivity(intent);
}
*/
} else if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.READ_EXTERNAL_STORAGE)) {
// Provide additional rationale to the user if the permission was not granted
String message = activity.getApplicationContext().getString(R.string.request_storage_permission);
Toast.makeText(activity.getApplicationContext(), message ,
Toast.LENGTH_LONG).show();
ActivityCompat.requestPermissions(activity,
new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE },
PERMISSION_REQUEST_STORAGE);
} else {
// Request the permission. The result will be received in onRequestPermissionsResult().
ActivityCompat.requestPermissions(activity,
new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE },
PERMISSION_REQUEST_STORAGE);
}
}

public static void showPermissionDenied(Context context) {
// Permission request denied
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
dialogBuilder.setTitle(String.format(context.getString(R.string.title_add_keyboard)));
dialogBuilder.setMessage(String.format(context.getString(R.string.storage_permission_denied2)));
dialogBuilder.setPositiveButton(context.getString(R.string.label_ok), null);
AlertDialog dialog = dialogBuilder.create();
dialog.show();

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

package com.tavultesoft.kmapro;

import static android.content.pm.PackageManager.PERMISSION_GRANTED;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
Expand Down Expand Up @@ -50,11 +52,17 @@
import android.net.Uri.Builder;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;

import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.widget.Toolbar;
import androidx.appcompat.app.AlertDialog;
import android.content.ClipData;
Expand All @@ -75,7 +83,9 @@
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.provider.Settings;
import android.text.Html;
import android.util.Log;
import android.util.TypedValue;
Expand All @@ -101,7 +111,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
public static Context context;

// Fields used for installing kmp packages
private static final int PERMISSION_REQUEST_STORAGE = 0;
public static final int PERMISSION_REQUEST_STORAGE = 0;
public static final int READ_REQUEST_CODE = 42;

private static final String TAG = "MainActivity";
Expand Down Expand Up @@ -256,7 +266,12 @@ protected void onResume() {
// file:// Chrome downloads and Filebrowsers
case "content":
case "file":
checkStoragePermission(loadingIntentUri);
requestPermissionIntentUri = loadingIntentUri;
if (CheckPermissions.isPermissionOK(this)) {
useLocalKMP(context, loadingIntentUri);
} else {
CheckPermissions.requestPermission(this, context);
}
break;
case "http" :
case "https" :
Expand Down Expand Up @@ -784,51 +799,13 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis
if (requestCode == PERMISSION_REQUEST_STORAGE) {
// Request for storage permission
if (grantResults.length == 1 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
grantResults[0] == PERMISSION_GRANTED) {
// Permission has been granted. Resume task needing this permission
useLocalKMP(context, requestPermissionIntentUri);
} else {
// Permission request denied
String message = getString(R.string.storage_permission_denied);
Toast.makeText(getApplicationContext(), message,
Toast.LENGTH_SHORT).show();
}
}
}

private void checkStoragePermission(Uri data) {
// Check if the Storage permission has been granted
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
useLocalKMP(context, data);
} else {
// Permission is missing and must be requested
requestPermissionIntentUri = data;
requestStoragePermission();
CheckPermissions.showPermissionDenied(context);
}
} else {
// Permission automatically granted on older Android versions
useLocalKMP(context, data);
}
}

/**
* Requests the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permissions
*/
private void requestStoragePermission() {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
// Provide additional rationale to the user if the permission was not granted
String message = getString(R.string.request_storage_permission);
Toast.makeText(getApplicationContext(), message ,
Toast.LENGTH_LONG).show();
ActivityCompat.requestPermissions(this,
new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE },
PERMISSION_REQUEST_STORAGE);
} else {
// Request the permission. The result will be received in onRequestPermissionsResult().
ActivityCompat.requestPermissions(this,
new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE },
PERMISSION_REQUEST_STORAGE);
}
}

Expand Down
3 changes: 3 additions & 0 deletions android/KMAPro/kMAPro/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@
<string name="storage_permission_denied" comment="Keyboard package installation may fail since Android storage permission not granted">
Storage permission request was denied. May fail to install keyboard package</string>

<string name="storage_permission_denied2" comment="Keyboard package installation failed. Recommend install from local file">
Storage permission request failed. Try Keyman Settings - Install from local file</string>

<!-- Context: Keyman Settings menu -->
<string name="keyman_settings" comment="Settings menu title">Settings</string>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,12 +297,13 @@ private static void performLeftDeletions(InputConnection ic, int dn) {
expectedChars = "";
}
ic.deleteSurroundingText(dn + numPairs, 0);
CharSequence newContext = getCharacterSequence(ic, originalBufferLength - 2*dn);
// Shorten the retrieved context by exactly as many characters as were just deleted.
CharSequence newContext = getCharacterSequence(ic, originalBufferLength - dn - numPairs);

CharSequence charsToRestore = CharSequenceUtil.restoreChars(expectedChars, newContext);
if (charsToRestore.length() > 0) {
// Restore expectedChars that Chromium deleted.
// Use newCusorPosition 1 so cursor will be after the inserted string
// Use newCursorPosition 1 so cursor will be after the inserted string
ic.commitText(charsToRestore, 1);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ public static CharSequence restoreChars(CharSequence expectedChars, CharSequence
if (expectedChars.length() != currentContext.length()) {
String expectedCharsString = expectedChars.toString();
String currentContextString = currentContext.toString();
int index = expectedCharsString.indexOf(currentContextString);
int index = expectedCharsString.lastIndexOf(currentContextString);
if (currentContextString.length() == 0) {
index = 0;
}
if (index > -1) {
// subSequence indices are start(inclusive) to end(exclusive)
charsToRestore = expectedChars.subSequence(index + currentContextString.length(), expectedChars.length());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,5 +151,12 @@ public void test_restoreChars_expected_chars() {
Assert.assertEquals("p" + COMPOSING_CIRCUMFLEX_ACCENT + "p" + COMPOSING_CIRCUMFLEX_ACCENT, charsToRestore);
}

@Test
public void test_repeated_char_backspace() {
CharSequence currentContext = "----------------";
CharSequence expectedChars = "-----------------";
CharSequence charsToRestore = CharSequenceUtil.restoreChars(expectedChars, currentContext);
Assert.assertEquals("", charsToRestore);
}
//endregion
}
1 change: 1 addition & 0 deletions android/help/about/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ title: About Keyman
* [Welcome to Keyman](welcome)
* [What's New](whatsnew)
* [System Requirements](system-requirements)
* [Data Privacy Policy](privacy)
* [Version History](history)
34 changes: 34 additions & 0 deletions android/help/about/privacy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
title: Data Privacy Policy
---

## Privacy Policy for Keyman

This application will send crash reporting information to https://sentry.keyman.com. No personally identifiable information or keyboard strokes are recorded or shared.

You can disable crash reporting on the Keyman
[settings menu](../basic/config/)

### Data Deletion

Crash reporting information is retained for 90 days.

Keyman does not use user accounts, so we are unable to identify which crash reports associated with you or your device can be deleted.



### Developer Information

Keyman is developed and published by SIL International.

You can contact SIL or provide any feedback using the app store.

https://play.google.com/store/apps/details?id=com.tavultesoft.kmapro

The full SIL software privacy policy is at

https://software.sil.org/language-software-privacy-policy/

This Keyman data privacy policy is also available at

https://help.keyman.com/products/android/current-version/about/privacy
Loading

0 comments on commit 62b3e3b

Please sign in to comment.