gui: add persistent notification and run NfdService in foreground to improve stability

Change-Id: I47e7ff07888e4638a75ddb607d335511467a7779
refs: #4766
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 51c923b..9705135 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -10,6 +10,7 @@
     <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
 
     <application
         android:allowBackup="true"
diff --git a/app/src/main/java/net/named_data/nfd/MainActivity.java b/app/src/main/java/net/named_data/nfd/MainActivity.java
index a1de558..616d562 100644
--- a/app/src/main/java/net/named_data/nfd/MainActivity.java
+++ b/app/src/main/java/net/named_data/nfd/MainActivity.java
@@ -19,6 +19,7 @@
 
 package net.named_data.nfd;
 
+import android.content.Intent;
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
@@ -115,6 +116,22 @@
     }
   }
 
+  @Override
+  protected void onNewIntent(Intent intent) {
+    super.onNewIntent(intent);
+    setIntent(intent);
+    FragmentManager fragmentManager = getSupportFragmentManager();
+
+    int drawItem = getIntent().getIntExtra(INTENT_KEY_FRAGMENT_TAG, -1);
+    if (drawItem == DRAWER_ITEM_GENERAL) {
+      MainFragment mainFragment =  MainFragment.newInstance();
+      fragmentManager
+          .beginTransaction()
+          .replace(R.id.main_fragment_container, mainFragment)
+          .commit();
+    }
+  }
+
   /**
    * Convenience method that replaces the main fragment container with the
    * new fragment and adding the current transaction to the backstack.
@@ -201,4 +218,7 @@
   public static final int DRAWER_ITEM_PING = 4;
   //public static final int DRAWER_ITEM_STRATEGIES = X;
   public static final int DRAWER_ITEM_WIFIDIRECT = 5;
+
+  /** Indent key for jump to a fragment */
+  public static final String INTENT_KEY_FRAGMENT_TAG = "fragmentTag";
 }
diff --git a/app/src/main/java/net/named_data/nfd/service/NfdService.java b/app/src/main/java/net/named_data/nfd/service/NfdService.java
index 79d18ca..5b85a6e 100644
--- a/app/src/main/java/net/named_data/nfd/service/NfdService.java
+++ b/app/src/main/java/net/named_data/nfd/service/NfdService.java
@@ -1,6 +1,6 @@
 /* -*- Mode:jde; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
 /**
- * Copyright (c) 2015-2017 Regents of the University of California
+ * Copyright (c) 2015-2018 Regents of the University of California
  * <p>
  * This file is part of NFD (Named Data Networking Forwarding Daemon) Android.
  * See AUTHORS.md for complete list of NFD Android authors and contributors.
@@ -19,10 +19,16 @@
 
 package net.named_data.nfd.service;
 
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Color;
 import android.os.AsyncTask;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
@@ -39,6 +45,7 @@
 import com.intel.jndn.management.types.RibEntry;
 
 import net.named_data.jndn.Name;
+import net.named_data.nfd.MainActivity;
 import net.named_data.nfd.MainFragment;
 import net.named_data.nfd.R;
 import net.named_data.nfd.utils.G;
@@ -179,6 +186,20 @@
     m_nfdServiceMessenger = null;
   }
 
+  /**
+   * Ensure the persistent notification will be removed after the app is swiped out.
+   *
+   * @param rootIntent
+   */
+  @Override
+  public void onTaskRemoved(Intent rootIntent) {
+    super.onTaskRemoved(rootIntent);
+
+    stopForeground(true);
+
+    stopSelf();
+  }
+
   /////////////////////////////////////////////////////////////////////////////
 
   /**
@@ -207,6 +228,7 @@
       // from a Handler's message through binding with the service.
       startService(new Intent(this, NfdService.class));
       G.Log(TAG, "serviceStartNfd()");
+      startForeground(NOTIFICATION_ID, createNotification());
     } else {
       G.Log(TAG, "serviceStartNfd(): NFD Service already running!");
     }
@@ -259,6 +281,7 @@
       SharedPreferencesManager.clearFaceIds(getApplicationContext());
       stopSelf();
       G.Log(TAG, "serviceStopNfd()");
+      stopForeground(true);
     }
   }
 
@@ -456,6 +479,46 @@
   }
 
   /**
+   * Create a persistent notification to indicate NFD is running.
+   */
+  private Notification
+  createNotification() {
+    NotificationManager notificationManager =
+        (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
+    Intent intent = new Intent(getApplicationContext(), MainActivity.class);
+    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+    intent.putExtra(MainActivity.INTENT_KEY_FRAGMENT_TAG, MainActivity.DRAWER_ITEM_GENERAL);
+
+    PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(),
+                                                            NOTIFICATION_ID, intent,
+                                                            PendingIntent.FLAG_UPDATE_CURRENT);
+
+    Notification.Builder builder;
+    // If devices's sdk version is >= 26, we need to create a channel for notification.
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+      NotificationChannel channel =
+          new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
+      channel.setDescription(getApplicationContext().getResources().getString(R.string.channel_description));
+      channel.setVibrationPattern(new long[]{0});
+      channel.enableVibration(true);
+      notificationManager.createNotificationChannel(channel);
+      builder = new Notification.Builder(getApplicationContext(), CHANNEL_ID);
+    } else {
+      builder = new Notification.Builder(getApplicationContext());
+      builder.setVibrate(new long[]{0L});
+    }
+    builder
+        .setContentTitle(getApplicationContext().getResources().getString(R.string.notification_content_title))
+        .setContentText(getApplicationContext().getResources().getString(R.string.notification_content_text))
+        .setContentIntent(pendingIntent)
+        .setSmallIcon(R.drawable.nfd_notification)
+        .setColor(getApplication().getColor(R.color.notification_color_orange))
+        .setOngoing(true);
+
+    return builder.build();
+  }
+
+  /**
    * Messenger to handle messages that are passed to the NfdService
    */
   private Messenger m_nfdServiceMessenger = null;
@@ -469,4 +532,19 @@
    * Handler to deal with timeout behaviors
    */
   private Handler m_handler = new Handler();
+
+  /**
+   * Unique notification ID
+   */
+  private static final int NOTIFICATION_ID = 7;
+
+  /**
+   * Unique notification channel ID
+   */
+  private static final String CHANNEL_ID = "0404";
+
+  /**
+   * User visible notification channel name
+   */
+  private static final String CHANNEL_NAME = "nfd-channel";
 }
diff --git a/app/src/main/res/drawable-hdpi/nfd_notification.png b/app/src/main/res/drawable-hdpi/nfd_notification.png
new file mode 100644
index 0000000..a93def4
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/nfd_notification.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/nfd_notification.png b/app/src/main/res/drawable-mdpi/nfd_notification.png
new file mode 100644
index 0000000..b51fea8
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/nfd_notification.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/nfd_notification.png b/app/src/main/res/drawable-xhdpi/nfd_notification.png
new file mode 100644
index 0000000..588f755
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/nfd_notification.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/nfd_notification.png b/app/src/main/res/drawable-xxhdpi/nfd_notification.png
new file mode 100644
index 0000000..a099777
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/nfd_notification.png
Binary files differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index b9519a1..c5320ab 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -2,4 +2,5 @@
 <resources>
     <item name="ndn_color_fire_bush" type="color">#DB9710</item>
     <item name="android_color_gray" type="color">#C6C6C6</item>
+    <item name="notification_color_orange" type="color">#CD5F31</item>
 </resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 47032af..eb7b812 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -114,4 +114,8 @@
     <string name="fragment_route_details_next_hops">List of next hops</string>
     <string name="fragment_route_route_name_title">Route Name</string>
     <string name="ping_client_prefix_hint">Prefix (e.g., /ndn/edu/arizona)</string>
+
+    <string name="notification_content_title">Status</string>
+    <string name="notification_content_text">NFD is running</string>
+    <string name="channel_description">NDN Android notification.</string>
 </resources>