Tips for Developing a Standing up Reminder
This article illustrates how to create a standing up reminder using sample code, to help break the bad habit of sitting too long.
Join the DZone community and get the full member experience.
Join For FreeCheck this out: Are you bending like this at your desk?
Well, I am, and if you're like me, maybe we should get up and move around for a little while.
Joking aside, I know COVID-19 forced many of you to work from home. As a result, many of us have started to live a sedentary lifestyle. After reading a bunch of posts shared by my family describing how harmful sitting too long is, I decided to change this habit by developing a function that reminds me to move around, like this:
Development Overview
To develop such a function, I turned to the mobile context-awareness capabilities from Awareness Kit. I used a time awareness capability and behavior awareness capability to create a time barrier and behavior detection barrier, respectively, as well as a combination of the barriers.
More specifically, these include:
- Time awareness capability: TimeBarrier.duringTimePeriod(long startTimeStamp, long endSecondsMillis); is used to define a time barrier. If the current time is within the range from startTimeStamp to endSecondsMillis, the barrier status is true. Otherwise, it is false.
- Behavior awareness capability: BehaviorBarrier.keeping(BehaviorBarrier.BEHAVIOR_STILL); is used to define a behavior detection barrier. If the status of a user is still, the barrier status is true; if the status of a user changes — from being stationary to moving, for example — then the barrier will be triggered, and its status will be false.
- Barrier combination: Use and to combine the above two barriers into AwarenessBarrier.and(keepStillBarrier, timePeriodBarrier). When the current time of a user is within the specified time segment, and their status is still, the barrier status will be true. Otherwise, it is false.
It's quite straightforward, right? Let's take a deeper look into how the function is developed.
Development Procedure
Making Preparations
1. Create an Android Studio project. Put agconnect-services.json and the app signing certificate to the app's root directory. If you need to know where to obtain the two files, you can check the References section to get more information.
2. Configure a Maven repository address and import a plugin.
buildscript {
repositories {
maven { url 'http://szxy1.artifactory.cd-cloud-artifact.tools.huawei.com/artifactory/sz-maven-public/' }
maven { url 'http://dgg.maven.repo.cmc.tools.huawei.com/artifactory/Product-CloudTest-snapshot/' }
maven { url 'http://dgg.maven.repo.cmc.tools.huawei.com/artifactory/Product-cloudserviceSDK-release/' }
maven { url 'http://artifactory.cde.huawei.com/artifactory/Product-Binary-Release/' }
maven { url 'http://language.cloudartifact.dgg.dragon.tools.huawei.com/artifactory/product_maven/' }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.3'
classpath 'com.huawei.agconnect:agcp:1.0.0.300'
}
}
allprojects {
repositories {
maven { url 'http://szxy1.artifactory.cd-cloud-artifact.tools.huawei.com/artifactory/sz-maven-public/' }
maven { url 'http://dgg.maven.repo.cmc.tools.huawei.com/artifactory/Product-CloudTest-snapshot/' }
maven { url 'http://dgg.maven.repo.cmc.tools.huawei.com/artifactory/Product-cloudserviceSDK-release/' }
maven { url 'http://artifactory.cde.huawei.com/artifactory/Product-Binary-Release/' }
maven { url 'http://language.cloudartifact.dgg.dragon.tools.huawei.com/artifactory/product_maven/' }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
3. Open the app-level build. gradle file, add the plugin, configure the signing certificate parameters, and add necessary building dependencies.
apply plugin: 'com.android.application'
apply plugin: 'com.huawei.agconnect'
android {
compileSdkVersion 31
buildToolsVersion "31.0.0"
defaultConfig {
applicationId "com.huawei.smartlifeassistant"
minSdkVersion 26
targetSdkVersion 31
versionCode 2
versionName "2.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
release {
storeFile file('Awareness.jks')
keyAlias 'testKey'
keyPassword 'lhw123456'
storePassword 'lhw123456'
v1SigningEnabled true
v2SigningEnabled true
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
signingConfig signingConfigs.release
debuggable true
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.huawei.agconnect:agconnect-core:1.5.2.300'
implementation 'com.huawei.hms:awareness:3.1.0.301'
}
4. Make sure that the app package names in agconnect-services.json and the project are the same. Then, compile the project.
Requesting Dynamic Permissions
private static final int PERMISSION_REQUEST_CODE = 940;
private final String[] mPermissionsOnHigherVersion = new String[]{Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION,
Manifest.permission.ACTIVITY_RECOGNITION,
Manifest.permission.BLUETOOTH_CONNECT};
private final String[] mPermissionsOnLowerVersion = new String[]{Manifest.permission.ACCESS_FINE_LOCATION,
"com.huawei.hms.permission.ACTIVITY_RECOGNITION"};
private void checkAndRequestPermissions() {
List<String> permissionsDoNotGrant = new ArrayList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
for (String permission : mPermissionsOnHigherVersion) {
if (ActivityCompat.checkSelfPermission(this, permission)
!= PackageManager.PERMISSION_GRANTED) {
permissionsDoNotGrant.add(permission);
}
}
} else {
for (String permission : mPermissionsOnLowerVersion) {
if (ActivityCompat.checkSelfPermission(this, permission)
!= PackageManager.PERMISSION_GRANTED) {
permissionsDoNotGrant.add(permission);
}
}
}
if (permissionsDoNotGrant.size() > 0) {
ActivityCompat.requestPermissions(this,
permissionsDoNotGrant.toArray(new String[0]), PERMISSION_REQUEST_CODE);
}
}
Check whether the dynamic permissions are granted in onCreate of the activity.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sedentary_reminder);
setTitle(getString(R.string.life_assistant));
// Check whether the dynamic permissions are granted.
checkAndRequestPermissions();
//...
}
private void checkAndRequestPermissions() {
List<String> permissionsDoNotGrant = new ArrayList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
for (String permission : mPermissionsOnHigherVersion) {
if (ActivityCompat.checkSelfPermission(this, permission)
!= PackageManager.PERMISSION_GRANTED) {
permissionsDoNotGrant.add(permission);
}
}
} else {
for (String permission : mPermissionsOnLowerVersion) {
if (ActivityCompat.checkSelfPermission(this, permission)
!= PackageManager.PERMISSION_GRANTED) {
permissionsDoNotGrant.add(permission);
}
}
}
if (permissionsDoNotGrant.size() > 0) {
ActivityCompat.requestPermissions(this,
permissionsDoNotGrant.toArray(new String[0]), PERMISSION_REQUEST_CODE);
}
}
Using the Broadcast Message to Create PendingIntent Which Is Triggered When the Barrier Status Changes, and Registering a Broadcast Receiver
final String barrierReceiverAction = getApplication().getPackageName() + "COMBINED_BARRIER_RECEIVER_ACTION";
Intent intent = new Intent(barrierReceiverAction);
// Also, we can use getActivity() or getService() to create PendingIntent.
// This depends on what action you want to be triggered when the barrier status changes.
mPendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
| PendingIntent.FLAG_MUTABLE);
// Register a broadcast receiver to receive the broadcast when the barrier status changes.
mBarrierReceiver = new CombinedBarrierReceiver();
registerReceiver(mBarrierReceiver, new IntentFilter(barrierReceiverAction));
final class CombinedBarrierReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
BarrierStatus barrierStatus = BarrierStatus.extract(intent);
String label = barrierStatus.getBarrierLabel();
int barrierPresentStatus = barrierStatus.getPresentStatus();
if (label == null) {
return;
}
switch (label) {
case COMBINED_BEHAVIOR_TIME_BARRIER_LABEL:
if (barrierPresentStatus == BarrierStatus.FALSE) {
if (System.currentTimeMillis() - lastTime >= tenSecondsMillis) {
alert.show();
}
updateTimeAwarenessBarrier();
}
break;
default:
break;
}
}
}
Registering or Deleting the Barrier Combination
Use a switch on the UI to register or delete the barrier combination.
automaticAdjustSwitch = findViewById(R.id.sedentary_reminder_switch);
automaticAdjustSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
startAutomaticAdust(isChecked);
}
});
private void startAutomaticAdust(boolean isChecked) {
if (isChecked) {
addBarriers();
} else {
deleteBarriers();
}
}
private void addBarriers() {
keepStillBarrier = BehaviorBarrier.keeping(BehaviorBarrier.BEHAVIOR_STILL);
updateTimeAwarenessBarrier();
}
@NonNull
private void updateTimeAwarenessBarrier() {
long currentTimeStamp = System.currentTimeMillis();
lastTime = currentTimeStamp;
AwarenessBarrier timePeriodBarrier = TimeBarrier.duringTimePeriod(currentTimeStamp, currentTimeStamp + tenSecondsMillis);
AwarenessBarrier combinedTimeBluetoothBarrier = AwarenessBarrier.and(keepStillBarrier, timePeriodBarrier);
Utils.addBarrier(this, COMBINED_BEHAVIOR_TIME_BARRIER_LABEL,
combinedTimeBluetoothBarrier, mPendingIntent);
}
private void deleteBarriers() {
Utils.deleteBarrier(this, mPendingIntent);
}
Showing the Reminding Information
Use an AlertDialog to remind a user.
// Initialize Builder.
builder = new AlertDialog.Builder(this);
// Load and configure the custom view.
final LayoutInflater inflater = getLayoutInflater();
View view_custom = inflater.inflate(R.layout.view_dialog_custom, null, false);
builder.setView(view_custom);
builder.setCancelable(false);
alert = builder.create();
view_custom.findViewById(R.id.i_kown).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
alert.dismiss();
}
});
final class CombinedBarrierReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
BarrierStatus barrierStatus = BarrierStatus.extract(intent);
String label = barrierStatus.getBarrierLabel();
int barrierPresentStatus = barrierStatus.getPresentStatus();
if (label == null) {
return;
}
switch (label) {
case COMBINED_BEHAVIOR_TIME_BARRIER_LABEL:
if (barrierPresentStatus == BarrierStatus.FALSE) {
if (System.currentTimeMillis() - lastTime >= tenSecondsMillis) {
alert.show();
}
updateTimeAwarenessBarrier();
}
break;
default:
break;
}
}
}
And just like that, the standing up reminder function is created.
In fact, I've got some more ideas for using mobile context-awareness capabilities, such as developing a sleep reminder using the ambient light awareness capability and the time awareness capability. This reminder can notify users when it is bedtime based on a specified time and when the ambient brightness is lower than a specified value.
A schedule reminder also sounds like a good idea, which uses the time awareness capability to tell a user their schedule for a day at a specified time.
These are just some of my ideas. If you've got some other interesting inspirations for using the context-awareness capabilities, please share them in the comments section below and see how our ideas overlap.
Published at DZone with permission of Jackson Jiang. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments