Tips for Developing a Standing up Reminder
Tags: java, reminder, programming, hmscore
Published: 2022-04-28 01:25:51

Check this out: Are you bending like this at your desk?

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t62j9fnh1glwbn20x83j.png)

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:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e2rrfnw7twvdxk9ykx79.png)

## Development Overview
To develop such a function, I turned to the mobile context-awareness capabilities. 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 included:
i. 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**.
ii. 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**.
iii. 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
i. 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.
ii. 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
}
```
iii. 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'
}
```
iiii. 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.

## References
[The dangers of sitting](https://www.health.harvard.edu/pain/the-dangers-of-sitting)
[What are the risks of sitting too much?](https://www.mayoclinic.org/healthy-lifestyle/adult-health/expert-answers/sitting/faq-20058005)
Obtaining [agconnect-services.json](https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/integrating-sdk-0000001050031152)
Obtaining a [signing certificate](https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/config-agc-0000001050033101)