New version update notification

Notify user when a new version of browser is available internally.

Change-Id: I29ae1443c473781c3227a574ee42eb5f7e1be36c
diff --git a/src/com/android/browser/UpdateNotificationService.java b/src/com/android/browser/UpdateNotificationService.java
new file mode 100644
index 0000000..dc91315
--- /dev/null
+++ b/src/com/android/browser/UpdateNotificationService.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package com.android.browser;
+
+import android.app.IntentService;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.TaskStackBuilder;
+import android.content.Intent;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.support.v4.app.NotificationCompat;
+import android.util.Log;
+
+
+import org.codeaurora.swe.BrowserCommandLine;
+import org.codeaurora.swe.Engine;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONTokener;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLConnection;
+
+public class UpdateNotificationService extends IntentService {
+    private static final String LOGTAG = "UpdateNotificationService";
+    private static final String ACTION_CHECK_UPDATES = BrowserConfig.AUTHORITY +
+            ".action.check.update";
+    public static final int DEFAULT_UPDATE_INTERVAL = 604800000; // one week
+    public static final String UPDATE_SERVICE_PREF = "browser_update_service";
+    public static final String UPDATE_JSON_VERSION_CODE = "versioncode";
+    public static final String UPDATE_JSON_VERSION_STRING = "versionstring";
+    public static final String UPDATE_JSON_MIN_INTERVAL = "interval";
+    public static final String UPDATE_INTERVAL = "update_interval";
+    public static final String UPDATE_VERSION_CODE = "version_code";
+    public static final String UPDATE_VERSION = "update_version";
+    public static final String UPDATE_URL = "update_url";
+    public static final String UPDATE_TIMESTAMP = "update_timestamp";
+    private static int NOTIFICATION_ID = 1000;
+    private static boolean sIntentServiceInitialized = false;
+    private static boolean sNotifyAlways = false;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        initEngine(this);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (sIntentServiceInitialized)
+            Engine.pauseTracing(this);
+    }
+
+    private static void initEngine(Context context) {
+        if (!EngineInitializer.isInitialized()) {
+            sIntentServiceInitialized = true;
+            EngineInitializer.initializeSync((Context) context);
+        }
+    }
+
+    public static void startActionUpdateNotificationService(Context context) {
+        Intent intent = new Intent(context, UpdateNotificationService.class);
+        intent.setAction(ACTION_CHECK_UPDATES);
+        context.startService(intent);
+    }
+
+    public static String getFlavor(Context ctx) {
+        String flavor = "";
+        try {
+            ApplicationInfo ai = ctx.getPackageManager().getApplicationInfo(
+                    ctx.getPackageName(),PackageManager.GET_META_DATA);
+            String compiler = (String) ai.metaData.get("Compiler");
+            String arch = (String) ai.metaData.get("Architecture");
+            flavor = "url-" + compiler + "-" + arch;
+        } catch (Exception e) {
+            Log.e(LOGTAG, "getFlavor Exception : " + e.toString());
+        }
+        return flavor;
+    }
+
+    public static void updateCheck(Context context) {
+        initEngine(context.getApplicationContext());
+        if (!BrowserCommandLine.hasSwitch(BrowserSwitches.AUTO_UPDATE_SERVER_CMD)) {
+            if (Browser.LOGV_ENABLED)
+                Log.v(LOGTAG, "skip no command line: ");
+            return;
+        }
+        long interval = getInterval(context);
+        Long last_update_time = getLastUpdateTimestamp(context);
+        if ((last_update_time +  interval) < System.currentTimeMillis()) {
+            if (Browser.LOGV_ENABLED)
+                Log.v(LOGTAG, "check for update now: ");
+            startActionUpdateNotificationService(context);
+        }
+    }
+
+    public static int getLatestVersionCode(Context ctx) {
+        SharedPreferences sharedPref = ctx.getSharedPreferences(
+                UPDATE_SERVICE_PREF, Context.MODE_PRIVATE);
+        return sharedPref.getInt(UPDATE_VERSION_CODE, 0);
+    }
+
+    public static String getLatestDownloadUrl(Context ctx) {
+        SharedPreferences sharedPref = ctx.getSharedPreferences(
+                UPDATE_SERVICE_PREF, Context.MODE_PRIVATE);
+        return sharedPref.getString(UPDATE_URL,"");
+    }
+
+    public static String getLatestVersion(Context ctx) {
+        SharedPreferences sharedPref = ctx.getSharedPreferences(
+                UPDATE_SERVICE_PREF, Context.MODE_PRIVATE);
+        return sharedPref.getString(UPDATE_VERSION, "");
+    }
+
+    private static long getLastUpdateTimestamp(Context ctx) {
+        SharedPreferences sharedPref = ctx.getSharedPreferences(
+                UPDATE_SERVICE_PREF, Context.MODE_PRIVATE);
+        return sharedPref.getLong(UPDATE_TIMESTAMP, 0);
+    }
+
+    private static int getInterval(Context ctx) {
+        SharedPreferences sharedPref = ctx.getSharedPreferences(
+                UPDATE_SERVICE_PREF, Context.MODE_PRIVATE);
+        return sharedPref.getInt(UPDATE_INTERVAL, DEFAULT_UPDATE_INTERVAL);
+    }
+
+    public UpdateNotificationService() {
+        super("UpdateNotificationService");
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        if (intent != null) {
+            final String action = intent.getAction();
+            if (ACTION_CHECK_UPDATES.equals(action)) {
+                handleUpdateCheck();
+            }
+        }
+    }
+
+    private void updateTimeStamp() {
+        SharedPreferences sharedPref = getSharedPreferences(
+                UPDATE_SERVICE_PREF, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = sharedPref.edit();
+        editor.putLong(UPDATE_TIMESTAMP, System.currentTimeMillis());
+        editor.commit();
+    }
+
+    private void persist(int versionCode, String url, String version, int interval) {
+        SharedPreferences sharedPref = getSharedPreferences(
+                UPDATE_SERVICE_PREF, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = sharedPref.edit();
+        editor.putInt(UPDATE_VERSION_CODE, versionCode);
+        editor.putInt(UPDATE_INTERVAL, interval);
+        editor.putString(UPDATE_VERSION, version);
+        editor.putString(UPDATE_URL, url);
+        if (Browser.LOGV_ENABLED) {
+            Log.v(LOGTAG, "persist version code : " + versionCode);
+            Log.v(LOGTAG, "persist version : " + version);
+            Log.v(LOGTAG, "persist download url : " + url);
+        }
+        editor.commit();
+    }
+
+    private void handleUpdateCheck() {
+        String server_url = BrowserCommandLine.getSwitchValue(
+                BrowserSwitches.AUTO_UPDATE_SERVER_CMD) + "/" + getPackageName();
+        int interval = DEFAULT_UPDATE_INTERVAL;
+        InputStream stream = null;
+        if (server_url != null && !server_url.isEmpty()) {
+            try {
+                URLConnection connection = new URL(server_url).openConnection();
+                stream = connection.getInputStream();
+                String result = readContents(stream);
+                if (Browser.LOGV_ENABLED)
+                    Log.v(LOGTAG, "handleUpdateCheck result : " + result);
+                JSONObject jsonResult = (JSONObject) new JSONTokener(result).nextValue();
+                int versionCode = Integer.parseInt((String) jsonResult.get(UPDATE_JSON_VERSION_CODE));
+                String url = (String) jsonResult.get(getFlavor(this));
+                String version = (String) jsonResult.get(UPDATE_JSON_VERSION_STRING);
+                if (jsonResult.has(UPDATE_JSON_MIN_INTERVAL))
+                    interval = Integer.parseInt((String) jsonResult.get(UPDATE_JSON_MIN_INTERVAL));
+                if (getCurrentVersionCode(this) < versionCode &&
+                        (sNotifyAlways || getLatestVersionCode(this) != versionCode)) {
+                    persist(versionCode, url, version, interval);
+                    // notify only once per version change
+                    showNotification(this, url, version);
+                }
+                stream.close();
+            } catch (JSONException e) {
+                Log.e(LOGTAG, "handleUpdateCheck JSONException : " + e.toString());
+            } catch (IOException e) {
+                Log.e(LOGTAG, "handleUpdateCheck IOException : " + e.toString());
+            } finally {
+                // always update the timestamp
+                updateTimeStamp();
+            }
+        }
+    }
+
+    public static int getCurrentVersionCode(Context ctx) {
+        PackageInfo pInfo = null;
+        try {
+            pInfo = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(LOGTAG, "getCurrentVersionCode Exception : " + e.toString());
+        }
+        return pInfo.versionCode;
+    }
+
+    private static void showNotification(Context ctx, String url, String version) {
+        NotificationCompat.Builder builder =
+                new NotificationCompat.Builder(ctx)
+                        .setSmallIcon(R.drawable.img_notify_update_white)
+                        .setContentTitle(ctx.getString(R.string.update))
+                        .setContentText(ctx.getString(R.string.update_msg) + version);
+        Intent resultIntent = new Intent(ctx, BrowserActivity.class);
+        resultIntent.setAction(Intent.ACTION_VIEW);
+        resultIntent.setData(Uri.parse(url));
+        TaskStackBuilder stackBuilder = TaskStackBuilder.create(ctx);
+        stackBuilder.addParentStack(BrowserActivity.class);
+        stackBuilder.addNextIntent(resultIntent);
+        PendingIntent resultPendingIntent =
+                stackBuilder.getPendingIntent(
+                        0,
+                        PendingIntent.FLAG_UPDATE_CURRENT
+                );
+        builder.setContentIntent(resultPendingIntent);
+        NotificationManager mNotificationManager =
+                (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);
+        Notification notification = builder.build();
+        notification.flags = Notification.DEFAULT_LIGHTS | Notification.FLAG_AUTO_CANCEL;
+        mNotificationManager.notify(NOTIFICATION_ID, notification);
+    }
+
+    private static void removeNotification(Context ctx) {
+        NotificationManager mNotificationManager =
+                (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE);
+        mNotificationManager.cancel(NOTIFICATION_ID);
+    }
+
+    private static String readContents(InputStream is) {
+        String line = null;
+        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+        StringBuilder sb = new StringBuilder();
+        try {
+            line = reader.readLine();
+            while (line != null) {
+                line = line.replaceFirst("channel = ","");
+                sb.append(line + "\n");
+                line = reader.readLine();
+            }
+        } catch (Exception e) {
+            Log.e(LOGTAG, "convertStreamToString Exception : " + e.toString());
+        } finally {
+            try {
+                is.close();
+            } catch (Exception e) {
+                Log.e(LOGTAG, "convertStreamToString Exception : " + e.toString());
+            }
+        }
+        return sb.toString();
+    }
+
+}