Add a new service to handle the change of a receiver module
Changing a receiver module triggers a (persistent) notification offering
the user a new calibration. This offer cannot be ignored, only delayed.
it will show up again on next boot or after one day if the user asks for a
reminder. Completing the calibration will remove the notification for good.
When clicked, the notification will start a new activity displaying the two
possible actions: setting a reminder or executing the calibration. If the
user chooses so, the regular calibration activity is started.
FPIIM-1575
Change-Id: Ia12dcf87eca06f115212e01368b457465fdd01fa
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e59a8a4..5cb12f5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -18,6 +18,10 @@
<uses-permission android:name="android.permission.STOP_APP_SWITCHES" />
<uses-permission android:name="android.permission.REBOOT" tools:ignore="ProtectedPermissions"/>
+ <permission
+ android:name="com.fairphone.psensor.permission.CALIBRATE_PROXIMITY_SENSOR"
+ android:protectionLevel="signature" />
+
<application
android:allowBackup="true"
android:label="@string/app_full_name"
@@ -65,6 +69,15 @@
</intent-filter>
</activity>
+ <activity
+ android:name=".ReceiverModuleChangedActivity"
+ android:label="@string/receiver_module_changed_title"
+ android:screenOrientation="portrait">
+ <intent-filter>
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<receiver
android:name="com.fairphone.psensor.BootUpReceiver"
android:enabled="true"
@@ -81,6 +94,11 @@
android:enabled="false"
android:exported="false" />
+ <service
+ android:name=".CalibrationService"
+ android:enabled="true"
+ android:exported="true"
+ android:permission="com.fairphone.psensor.permission.CALIBRATE_PROXIMITY_SENSOR" />
</application>
</manifest>
diff --git a/app/src/main/java/com/fairphone/psensor/BootUpReceiver.java b/app/src/main/java/com/fairphone/psensor/BootUpReceiver.java
index d88bba7..bfb50f6 100644
--- a/app/src/main/java/com/fairphone/psensor/BootUpReceiver.java
+++ b/app/src/main/java/com/fairphone/psensor/BootUpReceiver.java
@@ -23,6 +23,8 @@
if (CalibrationStatusHelper.isCalibrationPending(context)) {
CalibrationStatusHelper.setCalibrationCompleted(context);
}
+
+ CalibrationService.startActionHandleBootCompleted(context);
}
if (Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
UpdateFinalizerService.startActionShutdown(context);
diff --git a/app/src/main/java/com/fairphone/psensor/CalibrationService.java b/app/src/main/java/com/fairphone/psensor/CalibrationService.java
new file mode 100644
index 0000000..0b7ad84
--- /dev/null
+++ b/app/src/main/java/com/fairphone/psensor/CalibrationService.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 Fairphone B.V.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.fairphone.psensor;
+
+import android.app.AlarmManager;
+import android.app.IntentService;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.fairphone.psensor.helpers.CalibrationStatusHelper;
+import com.fairphone.psensor.notifications.ReceiverModuleChangedNotification;
+
+public class CalibrationService extends IntentService {
+
+ private static final String TAG = "CalibrationService";
+
+ private static final boolean DEBUG = false;
+
+ /**
+ * Grace period before firing a reminder (in milliseconds): 1 day.
+ */
+ private static final long REMINDER_GRACE_PERIOD_MS = 1000 * 60 * 60 * 24;
+
+ /**
+ * Action to handle a change of receiver module by scheduling a new calibration.
+ */
+ private static final String ACTION_HANDLE_RECEIVER_MODULE_CHANGED =
+ "com.fairphone.psensor.action.handle_receiver_module_changed";
+
+ /**
+ * Action to remind the user at a later point (next reboot or after a grace period) that there
+ * was a change of receiver module.
+ */
+ private static final String ACTION_REMIND_RECEIVER_MODULE_CHANGED_LATER =
+ "com.fairphone.psensor.action.remind_receiver_module_changed_later";
+
+ public static void startActionHandleBootCompleted(Context context) {
+ context.startService(
+ new Intent(context, CalibrationService.class).setAction(Intent.ACTION_BOOT_COMPLETED));
+ }
+
+ public static void startActionRemindReceiverModuleChangedLater(Context context) {
+ context.startService(
+ new Intent(context, CalibrationService.class)
+ .setAction(ACTION_REMIND_RECEIVER_MODULE_CHANGED_LATER));
+ }
+
+ public CalibrationService() {
+ super("CalibrationService");
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ if (intent != null) {
+ final String action = intent.getAction();
+
+ if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+ handleBootComplete();
+ } else if (ACTION_HANDLE_RECEIVER_MODULE_CHANGED.equals(action)) {
+ handleActionReceiverModuleChanged();
+ } else if (ACTION_REMIND_RECEIVER_MODULE_CHANGED_LATER.equals(action)) {
+ handleActionRemindReceiverModuleChangedLater();
+ } else {
+ Log.e(TAG, "Unknown action received: " + action);
+ }
+ }
+ }
+
+ /**
+ * Handle the boot completed action.
+ */
+ private void handleBootComplete() {
+ if (CalibrationStatusHelper.isCalibrationNeededAfterReceiverModuleChanged(this)) {
+ ReceiverModuleChangedNotification.show(this);
+ }
+ }
+
+ /**
+ * Handle the receiver module changed action.
+ */
+ private void handleActionReceiverModuleChanged() {
+ CalibrationStatusHelper.setCalibrationNeededAfterReceiverModuleChanged(this);
+
+ ReceiverModuleChangedNotification.show(this);
+ }
+
+ /**
+ * Handle the receiver module changed reminder action.
+ */
+ private void handleActionRemindReceiverModuleChangedLater() {
+ ReceiverModuleChangedNotification.dismiss(this);
+
+ // TODO Activate the boot-up receiver
+
+ ((AlarmManager) getSystemService(Context.ALARM_SERVICE))
+ .set(AlarmManager.RTC, System.currentTimeMillis() + REMINDER_GRACE_PERIOD_MS,
+ PendingIntent.getService(this, 0,
+ new Intent(this, CalibrationService.class)
+ .setAction(ACTION_HANDLE_RECEIVER_MODULE_CHANGED),
+ PendingIntent.FLAG_UPDATE_CURRENT));
+ if (DEBUG) Log.d(TAG, "Alarm set to trigger " + ACTION_HANDLE_RECEIVER_MODULE_CHANGED
+ + " once again in " + REMINDER_GRACE_PERIOD_MS / 1000 + " seconds");
+ }
+
+}
diff --git a/app/src/main/java/com/fairphone/psensor/ReceiverModuleChangedActivity.java b/app/src/main/java/com/fairphone/psensor/ReceiverModuleChangedActivity.java
new file mode 100644
index 0000000..8e3bb17
--- /dev/null
+++ b/app/src/main/java/com/fairphone/psensor/ReceiverModuleChangedActivity.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 Fairphone B.V.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.fairphone.psensor;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+
+public class ReceiverModuleChangedActivity extends Activity implements View.OnClickListener {
+
+ private Button mButtonLater;
+ private Button mButtonNext;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_receiver_module_changed);
+
+ mButtonLater = (Button) findViewById(R.id.button_later);
+ mButtonLater.setOnClickListener(this);
+ mButtonNext = (Button) findViewById(R.id.button_next);
+ mButtonNext.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mButtonNext == v) {
+ startCalibrationIntent();
+ // Do not dismiss the notification; using it again will resume the calibration activity
+ // until it is complete.
+ } else if (mButtonLater == v) {
+ CalibrationService.startActionRemindReceiverModuleChangedLater(this);
+ finish();
+ }
+ }
+
+ private void startCalibrationIntent() {
+ startActivity(new Intent()
+ .setClass(this, CalibrationActivity.class)
+ .setFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME | Intent.FLAG_ACTIVITY_SINGLE_TOP));
+ }
+
+}
diff --git a/app/src/main/java/com/fairphone/psensor/helpers/CalibrationStatusHelper.java b/app/src/main/java/com/fairphone/psensor/helpers/CalibrationStatusHelper.java
index 22797d7..4324215 100644
--- a/app/src/main/java/com/fairphone/psensor/helpers/CalibrationStatusHelper.java
+++ b/app/src/main/java/com/fairphone/psensor/helpers/CalibrationStatusHelper.java
@@ -76,6 +76,8 @@
context.getString(R.string.preference_file_key), Context.MODE_PRIVATE);
final SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putBoolean(
+ context.getString(R.string.preference_calibration_needed_after_receiver_module_changed), false);
editor.putBoolean(context.getString(R.string.preference_pending_calibration), false);
editor.apply();
@@ -91,4 +93,29 @@
editor.apply();
}
+
+ public static boolean isCalibrationNeededAfterReceiverModuleChanged(Context context) {
+ final SharedPreferences sharedPreferences = context.getSharedPreferences(
+ context.getString(R.string.preference_file_key), Context.MODE_PRIVATE);
+
+ return sharedPreferences.getBoolean(
+ context.getString(R.string.preference_calibration_needed_after_receiver_module_changed), false);
+ }
+
+ public static void setCalibrationNeededAfterReceiverModuleChanged(Context context) {
+ final SharedPreferences sharedPreferences = context.getSharedPreferences(
+ context.getString(R.string.preference_file_key), Context.MODE_PRIVATE);
+ final SharedPreferences.Editor editor = sharedPreferences.edit();
+
+ editor.putBoolean(
+ context.getString(R.string.preference_calibration_needed_after_receiver_module_changed), true);
+ /*
+ * Force any previous calibration done during this session, but before this call, to be
+ * invalid. We want to make sure the pending calibration was not done before we could detect
+ * a module change.
+ */
+ editor.putBoolean(context.getString(R.string.preference_pending_calibration), false);
+
+ editor.apply();
+ }
}
diff --git a/app/src/main/java/com/fairphone/psensor/notifications/ReceiverModuleChangedNotification.java b/app/src/main/java/com/fairphone/psensor/notifications/ReceiverModuleChangedNotification.java
new file mode 100644
index 0000000..76234ea
--- /dev/null
+++ b/app/src/main/java/com/fairphone/psensor/notifications/ReceiverModuleChangedNotification.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 Fairphone B.V.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.fairphone.psensor.notifications;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+
+import com.fairphone.psensor.R;
+import com.fairphone.psensor.ReceiverModuleChangedActivity;
+
+public class ReceiverModuleChangedNotification {
+
+ private static final int NOTIFICATION_ID = 1;
+
+ /**
+ * Dismiss the notification.
+ */
+ public static void dismiss(Context context) {
+ ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
+ .cancel(NOTIFICATION_ID);
+ }
+
+ /**
+ * Show a <strong>public</strong> notification requesting the end-user to calibrate the
+ * proximity sensor because the receiver module changed.
+ * <p>
+ * The notification is permanent and can only be dismissed through a call to
+ * {@link #dismiss(Context)}.
+ */
+ public static void show(Context context) {
+ final Notification notification = new Notification.Builder(context)
+ .setContentTitle(context.getString(R.string.receiver_module_changed_title))
+ .setContentText(context.getString(R.string.receiver_module_changed_summary))
+ .setSmallIcon(R.drawable.ic_stat_action_calibrate)
+ .setColor(context.getResources().getColor(R.color.theme_primary))
+ .setStyle(new Notification.BigTextStyle().bigText(
+ context.getString(R.string.receiver_module_changed_extended_summary)))
+ .setContentIntent(getPendingContentIntent(context))
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setPriority(Notification.PRIORITY_MAX)
+ .setOngoing(true)
+ .build();
+
+ ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
+ .notify(NOTIFICATION_ID, notification);
+ }
+
+ private static PendingIntent getPendingContentIntent(Context context) {
+ return PendingIntent.getActivity(context, 0,
+ new Intent(context, ReceiverModuleChangedActivity.class)
+ .setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | Intent.FLAG_ACTIVITY_NEW_TASK),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+}
diff --git a/app/src/main/res/drawable-hdpi/ic_stat_action_calibrate.png b/app/src/main/res/drawable-hdpi/ic_stat_action_calibrate.png
new file mode 100644
index 0000000..e15bfe2
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_stat_action_calibrate.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_stat_action_calibrate.png b/app/src/main/res/drawable-mdpi/ic_stat_action_calibrate.png
new file mode 100644
index 0000000..3ccd6a9
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_stat_action_calibrate.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_stat_action_calibrate.png b/app/src/main/res/drawable-xhdpi/ic_stat_action_calibrate.png
new file mode 100644
index 0000000..5c0f55d
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_stat_action_calibrate.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_stat_action_calibrate.png b/app/src/main/res/drawable-xxhdpi/ic_stat_action_calibrate.png
new file mode 100644
index 0000000..6ccc78f
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_stat_action_calibrate.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_stat_action_calibrate.png b/app/src/main/res/drawable-xxxhdpi/ic_stat_action_calibrate.png
new file mode 100644
index 0000000..38dc134
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_stat_action_calibrate.png
Binary files differ
diff --git a/app/src/main/res/layout/activity_receiver_module_changed.xml b/app/src/main/res/layout/activity_receiver_module_changed.xml
new file mode 100644
index 0000000..094f9ac
--- /dev/null
+++ b/app/src/main/res/layout/activity_receiver_module_changed.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ tools:context="com.fairphone.psensor.ReceiverModuleChangedActivity">
+
+ <include layout="@layout/header_receiver_module_changed" />
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/background"
+ android:padding="@dimen/main_padding">
+
+ <TextView
+ android:id="@+id/subtitle"
+ style="@style/TextBold16BlueDark"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/main_small_text_margin_start"
+ android:layout_marginEnd="@dimen/main_small_text_margin_end"
+ android:text="@string/your_receiver_module_has_been_replaced"
+ android:lineSpacingExtra="4sp"
+ android:textIsSelectable="false" />
+
+ <TextView
+ android:id="@+id/desc_1"
+ style="@style/TextRegular14BlueDark"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/subtitle"
+ android:layout_marginTop="@dimen/main_small_text_below_heading_margin"
+ android:layout_marginStart="@dimen/main_small_text_margin_start"
+ android:layout_marginEnd="@dimen/main_small_text_margin_end"
+ android:lineSpacingExtra="4sp"
+ android:text="@string/you_need_to_calibrate_the_proximity_sensor" />
+
+ <TextView
+ android:id="@+id/desc_2"
+ style="@style/TextRegular14BlueDark"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/desc_1"
+ android:layout_marginTop="@dimen/main_small_text_below_heading_margin"
+ android:layout_marginStart="@dimen/main_small_text_margin_start"
+ android:layout_marginEnd="@dimen/main_small_text_margin_end"
+ android:lineSpacingExtra="4sp"
+ android:text="@string/we_will_guide_you" />
+
+ <Button
+ android:id="@+id/button_later"
+ style="@style/ButtonWhiteGrey"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_above="@+id/button_next"
+ android:layout_marginBottom="@dimen/main_margin_small"
+ android:text="@string/later" />
+
+ <Button
+ android:id="@+id/button_next"
+ style="@style/ButtonWhiteBlue"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:text="@string/next" />
+
+ </RelativeLayout>
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/header_receiver_module_changed.xml b/app/src/main/res/layout/header_receiver_module_changed.xml
new file mode 100644
index 0000000..296a5b3
--- /dev/null
+++ b/app/src/main/res/layout/header_receiver_module_changed.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@color/background_header"
+ android:paddingLeft="10dp"
+ android:paddingRight="10dp">
+
+ <TextView
+ style="@style/TitleLightPrimary"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/header_bigger_height"
+ android:ellipsize="end"
+ android:gravity="center_vertical|center_horizontal"
+ android:lineSpacingExtra="10sp"
+ android:lines="3"
+ android:text="@string/receiver_module_successfully_changed_title"
+ android:background="@color/background_header"
+ android:textSize="30sp"/>
+
+</RelativeLayout>
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 2c49b92..e36b04d 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -3,6 +3,7 @@
<!-- HEADER -->
<dimen name="header_big_height">100dp</dimen>
+ <dimen name="header_bigger_height">175dp</dimen>
<dimen name="header_small_height">56dp</dimen>
<dimen name="header_big_text_size">34sp</dimen>
<dimen name="header_small_text_size">20sp</dimen>
@@ -15,5 +16,7 @@
<dimen name="main_padding_small">20dp</dimen>
<dimen name="main_small_text_margin_top">43dp</dimen>
<dimen name="main_small_text_below_heading_margin">20dp</dimen>
+ <dimen name="main_small_text_margin_start">5dp</dimen>
+ <dimen name="main_small_text_margin_end">5dp</dimen>
<dimen name="main_button_margin_top">8dp</dimen>
</resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 2b8f7dc..71705e3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -51,11 +51,21 @@
<string name="preference_successfully_calibrated" translatable="false">preference_successfully_calibrated</string>
<string name="NotificationTitle">Finalizing Update</string>
+ <string name="receiver_module_changed_title">Top module replaced</string>
+ <string name="receiver_module_changed_summary">Calibrate the proximity sensor.</string>
+ <string name="receiver_module_changed_extended_summary">The top module has been replaced. The proximity sensor needs to be calibrated.</string>
+ <string name="receiver_module_successfully_changed_title">Top module\nsuccessfully replaced</string>
+ <string name="your_receiver_module_has_been_replaced">Your top module has been replaced.</string>
+ <string name="you_need_to_calibrate_the_proximity_sensor">You need to calibrate the proximity sensor to ensure it works properly.</string>
+ <string name="we_will_guide_you">"We'll guide you through three simple steps."</string>
+ <string name="later">Remind me later</string>
+
<!-- FRAGMENT TAGS -->
<string name="fragment_tag_incompatible_device_dialog" translatable="false">incompatible_device</string>
<!-- SHARED PREFERENCES -->
<string name="preference_pending_calibration" translatable="false">preference_pending_calibration</string>
+ <string name="preference_calibration_needed_after_receiver_module_changed" translatable="false">preference_calibration_needed_after_receiver_module_changed</string>
<!-- EXTERNAL PACKAGES AND ACTIVITIES -->
<string name="package_fairphone_updater" translatable="false">com.fairphone.updater</string>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index d2d96e1..a1b99dc 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -28,6 +28,11 @@
<item name="android:textColor">@color/white</item>
</style>
+ <style name="ButtonWhiteGrey" parent="@style/Button">
+ <item name="android:background">@drawable/button_grey_background</item>
+ <item name="android:textColor">@color/white</item>
+ </style>
+
<style name="ButtonWhitePink" parent="@style/Button">
<item name="android:background">@drawable/button_pink_transparent_background</item>
<item name="android:textColor">@color/white</item>