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

Add support for Android 12 and for requesting SMS permission #262

Merged
merged 20 commits into from
May 9, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bdd2dba
Adding notes on how to open Settings Dialog page
latin-panda Apr 7, 2022
9a88c64
upgrade to Android 12
latin-panda Apr 8, 2022
f32bb0b
fixing manifest deprecated attributes and adding prominent permission…
latin-panda Apr 13, 2022
14646a6
Fixing warning of deprecation in webview onReceivedError
latin-panda Apr 13, 2022
acf655d
Removing unused code, left over from PhotoGrabber
latin-panda Apr 13, 2022
5c429fb
Fixing deprecation in the Alert (used in webapp countdown widget)
latin-panda Apr 13, 2022
313c037
Fixing deprecation of AsyncTask
latin-panda Apr 14, 2022
19dd877
Fixing unit test
latin-panda Apr 14, 2022
7c45495
Merge branch 'master' of https://github.com/medic/medic-android into …
latin-panda Apr 19, 2022
f42a4c2
Merge branch 'master' of https://github.com/medic/medic-android into …
latin-panda Apr 20, 2022
9084394
feedback
latin-panda Apr 20, 2022
fc4eee3
Refactor vibration code into dedicated Vibrator utility
jkuester Apr 26, 2022
bf75815
Update TaskRunner to be AsyncExecutor
jkuester Apr 27, 2022
e81ab00
Merge AppUrlVerificationTask into the AppUrlVerifier
jkuester Apr 27, 2022
07ec1d8
Use AppUrlVerifier.clean in SettingsStore
jkuester Apr 27, 2022
4e98a6d
Upgrade PMD
jkuester Apr 27, 2022
c012939
Try unignoring GoogleAppIndexingWarning and Instantiatable
jkuester Apr 27, 2022
b51dca9
Refactor SmsSender to move old service calls into separate impl
jkuester Apr 28, 2022
8db57b8
Null out sms message after it is sent
jkuester Apr 28, 2022
8da6be4
Remove unneeded SuppressWarnings
jkuester Apr 28, 2022
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ Checkout the release notes in the [Changelog](CHANGELOG.md) page, our you can se
Development guides are available in the "Android" section of the [Community Health Toolkit Docs Site](https://docs.communityhealthtoolkit.org/core/guides/android/). You will find instructions of how to setup your development environment, build and test new features, creates new branded apps, release, publish... and so on.


## Settings Dialog
To open the Settings Dialog page, tap 5 times with one finger on the screen and then swipe right with two fingers, make sure to do this sequence fast.

latin-panda marked this conversation as resolved.
Show resolved Hide resolved
## Copyright

Copyright 2013-2022 Medic Mobile, Inc. <[email protected]>.
Expand Down
16 changes: 8 additions & 8 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.1'
classpath 'com.android.tools.build:gradle:7.1.3'
classpath 'com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.5'
}
}
Expand Down Expand Up @@ -95,7 +95,7 @@ def getVersionName = {
}

android {
compileSdkVersion 30
compileSdkVersion 31
buildToolsVersion '30.0.3'
packagingOptions {
exclude 'META-INF/LICENSE'
Expand All @@ -104,7 +104,7 @@ android {

defaultConfig {
//noinspection OldTargetApi
targetSdkVersion 30
targetSdkVersion 31
minSdkVersion 21 // Android 5.0
versionCode getVersionCode()
versionName getVersionName()
Expand Down Expand Up @@ -394,18 +394,18 @@ dependencies {
// Latest version of androidx.core requires Android 12+
latin-panda marked this conversation as resolved.
Show resolved Hide resolved
// noinspection GradleDependency
implementation 'androidx.core:core:1.6.0'
implementation 'androidx.activity:activity:1.3.1'
implementation 'androidx.fragment:fragment:1.3.6'
implementation 'androidx.activity:activity:1.4.0'
implementation 'androidx.fragment:fragment:1.4.1'
compileOnly 'com.github.spotbugs:spotbugs-annotations:4.5.3'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-inline:4.0.0'
testImplementation 'com.google.android:android-test:4.1.1.4'
testImplementation 'org.robolectric:robolectric:4.7'
testImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha03'
testImplementation 'androidx.test.espresso:espresso-intents:3.5.0-alpha03'
testImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha05'
testImplementation 'androidx.test.espresso:espresso-intents:3.5.0-alpha05'
testImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-web:3.5.0-alpha03'
androidTestImplementation 'androidx.test.espresso:espresso-web:3.5.0-alpha05'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test:core:1.4.0'
Expand Down
9 changes: 8 additions & 1 deletion src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,16 @@
<!-- READ_EXTERNAL_STORAGE required if users want to include photos from their phone -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

<!-- SEND_SMS required if users want to send reports as SMS -->
<uses-permission android:name="android.permission.SEND_SMS"/>

<application android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:allowBackup="false"
android:fullBackupContent="@xml/backup_rules_sdk_30_and_lower"
android:dataExtractionRules="@xml/backup_rules"
jkuester marked this conversation as resolved.
Show resolved Hide resolved
android:largeHeap="true"
tools:ignore="GoogleAppIndexingWarning,LockedOrientationActivity,Instantiatable">
tools:ignore="GoogleAppIndexingWarning,Instantiatable,LockedOrientationActivity,UnusedAttribute">
jkuester marked this conversation as resolved.
Show resolved Hide resolved
<activity android:name="StartupActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
Expand All @@ -51,6 +56,8 @@
android:screenOrientation="portrait"/>
<activity android:name="RequestStoragePermissionActivity"
android:screenOrientation="portrait"/>
<activity android:name="RequestSendSmsPermissionActivity"
android:screenOrientation="portrait"/>
<activity android:name="AppUrlIntentActivity"
android:launchMode="singleInstance"
android:exported="true">
Expand Down
47 changes: 0 additions & 47 deletions src/main/java/medic/android/ActivityBackgroundTask.java

This file was deleted.

36 changes: 29 additions & 7 deletions src/main/java/org/medicmobile/webapp/mobile/Alert.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,42 @@
package org.medicmobile.webapp.mobile;

import static android.os.VibrationEffect.DEFAULT_AMPLITUDE;

import android.annotation.TargetApi;
import android.content.Context;
import android.media.MediaPlayer;
import android.os.Build;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorManager;

public class Alert {
private final MediaPlayer m;
private final Vibrator v;
private final MediaPlayer mediaPlayer;
private final Vibrator vibrator;

@SuppressWarnings("deprecation")
jkuester marked this conversation as resolved.
Show resolved Hide resolved
@TargetApi(31)
public Alert(Context context) {
mediaPlayer = MediaPlayer.create(context, R.raw.sound_alert);

public Alert(Context ctx) {
m = MediaPlayer.create(ctx, R.raw.sound_alert);
v = (Vibrator) ctx.getSystemService(Context.VIBRATOR_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
jkuester marked this conversation as resolved.
Show resolved Hide resolved
VibratorManager vibratorManager = (VibratorManager) context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE);
vibrator = vibratorManager.getDefaultVibrator();
} else {
vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
}
}

@SuppressWarnings("deprecation")
@TargetApi(26)
public void trigger() {
if(v != null) v.vibrate(1500L);
m.start();
if (vibrator != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createOneShot(1500L, DEFAULT_AMPLITUDE));
} else {
vibrator.vibrate(1500L);
}
}
mediaPlayer.start();
}
}
23 changes: 23 additions & 0 deletions src/main/java/org/medicmobile/webapp/mobile/AppUrlVerifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.concurrent.Callable;

import org.json.JSONException;
import org.json.JSONObject;

import static org.medicmobile.webapp.mobile.BuildConfig.DEBUG;
import static org.medicmobile.webapp.mobile.MedicLog.trace;
import static org.medicmobile.webapp.mobile.R.string.errAppUrl_apiNotReady;
import static org.medicmobile.webapp.mobile.R.string.errAppUrl_appNotFound;
Expand Down Expand Up @@ -87,3 +91,22 @@ public static AppUrlVerification failure(String appUrl, int failure) {
return new AppUrlVerification(appUrl, false, failure);
}
}

class AppUrlVerificationTask implements Callable<AppUrlVerification> {
jkuester marked this conversation as resolved.
Show resolved Hide resolved
private final String appUrl;

public AppUrlVerificationTask(String appUrl) {
this.appUrl = appUrl;
}

@Override
public AppUrlVerification call() {
trace(this, "AppUrlVerificationTask :: Executing call, appUrl=%s", appUrl);

if (DEBUG && (appUrl == null || appUrl.isEmpty())) {
jkuester marked this conversation as resolved.
Show resolved Hide resolved
throw new RuntimeException("AppUrlVerificationTask :: Cannot verify APP URL because it is not defined.");
}

return new AppUrlVerifier().verify(appUrl);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ protected void onActivityResult(int requestCd, int resultCode, Intent intent) {
case ACCESS_LOCATION_PERMISSION:
processLocationPermissionResult(resultCode);
return;
case ACCESS_SEND_SMS_PERMISSION:
this.smsSender.resumeProcess(resultCode);
return;
default:
trace(this, "onActivityResult() :: no handling for requestCode=%s", requestCode.name());
}
Expand Down Expand Up @@ -438,9 +441,10 @@ private void registerRetryConnectionBroadcastReceiver() {
public enum RequestCode {
ACCESS_LOCATION_PERMISSION(100),
ACCESS_STORAGE_PERMISSION(101),
CHT_EXTERNAL_APP_ACTIVITY(102),
GRAB_MRDT_PHOTO_ACTIVITY(103),
FILE_PICKER_ACTIVITY(104);
ACCESS_SEND_SMS_PERMISSION(102),
CHT_EXTERNAL_APP_ACTIVITY(103),
GRAB_MRDT_PHOTO_ACTIVITY(104),
FILE_PICKER_ACTIVITY(105);

private final int requestCode;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ public boolean sms_available() {
public void sms_send(String id, String destination, String content) throws Exception {
try {
// TODO we may need to do this on a background thread to avoid the browser UI from blocking while the SMS is being sent. Check.
smsSender.send(id, destination, content);
smsSender.send(new SmsSender.Sms(id, destination, content));
} catch(Exception ex) {
logException(ex);
throw ex;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package org.medicmobile.webapp.mobile;

import static android.Manifest.permission.SEND_SMS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static org.medicmobile.webapp.mobile.MedicLog.trace;

import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.view.Window;
import android.widget.TextView;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;

public class RequestSendSmsPermissionActivity extends FragmentActivity {

private final ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
if (isGranted) {
trace(this, "RequestSendSmsPermissionActivity :: User allowed Send SMS permission.");
setResult(RESULT_OK);
finish();
return;
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !shouldShowRequestPermissionRationale(SEND_SMS)) {
trace(
this,
"RequestSendSmsPermissionActivity :: User rejected Send SMS permission twice or has selected \"never ask again\"." +
" Sending user to the app's setting to manually grant the permission."
);
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", getPackageName(), null));
this.appSettingsLauncher.launch(intent);
return;
}

trace(this, "RequestSendSmsPermissionActivity :: User rejected Send SMS permission.");
setResult(RESULT_CANCELED);
finish();
});

private final ActivityResultLauncher<Intent> appSettingsLauncher =
registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (ContextCompat.checkSelfPermission(this, SEND_SMS) == PERMISSION_GRANTED) {
trace(this, "RequestSendSmsPermissionActivity :: User granted Send SMS permission from app's settings.");
setResult(RESULT_OK);
finish();
return;
}

trace(this, "RequestSendSmsPermissionActivity :: User didn't grant Send SMS permission from app's settings.");
setResult(RESULT_CANCELED);
finish();
});

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

this.requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.request_send_sms_permission);

String appName = getResources().getString(R.string.app_name);
String message = getResources().getString(R.string.sendSmsRequestMessage);
TextView field = findViewById(R.id.sendSmsMessageText);
field.setText(String.format(message, appName));
}

public void onClickAllow(View view) {
trace(this, "RequestSendSmsPermissionActivity :: User agree with prominent disclosure message.");
this.requestPermissionLauncher.launch(SEND_SMS);
}

public void onClickDeny(View view) {
trace(this, "RequestSendSmsPermissionActivity :: User disagree with prominent disclosure message.");
setResult(RESULT_CANCELED);
finish();
}
}
Loading