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/BrowserSwitches.java b/src/com/android/browser/BrowserSwitches.java
index 77b6f05..137b259 100644
--- a/src/com/android/browser/BrowserSwitches.java
+++ b/src/com/android/browser/BrowserSwitches.java
@@ -60,9 +60,12 @@
 
     public static final String CMD_LINE_SWITCH_FEEDBACK = "mail-feedback-to";
 
-    public static final String CMD_LINE_SWITCH_HELPURL  = "help-url";
+    public static final String CMD_LINE_SWITCH_HELPURL = "help-url";
 
     public static final String CMD_LINE_SWITCH_EULA_URL = "legal-eula-url";
 
     public static final String CMD_LINE_SWITCH_PRIVACY_POLICY_URL = "legal-privacy-policy-url";
-}
+
+    public static final String AUTO_UPDATE_SERVER_CMD = "auto-update-server";
+
+}
\ No newline at end of file
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index 6728274..dbbc164 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -40,7 +40,6 @@
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Rect;
@@ -65,7 +64,6 @@
 import android.speech.RecognizerIntent;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.Patterns;
 import android.view.ActionMode;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
@@ -104,8 +102,6 @@
 import com.android.browser.mynavigation.MyNavigationUtil;
 import com.android.browser.platformsupport.Browser;
 import com.android.browser.platformsupport.BrowserContract;
-import com.android.browser.platformsupport.WebAddress;
-import com.android.browser.platformsupport.BrowserContract.Images;
 import com.android.browser.preferences.AboutPreferencesFragment;
 import com.android.browser.provider.BrowserProvider2.Thumbnails;
 import com.android.browser.provider.SnapshotProvider.Snapshots;
@@ -787,6 +783,7 @@
             Log.e(LOGTAG, "BrowserActivity is already resumed.");
             return;
         }
+        UpdateNotificationService.updateCheck(mActivity);
         mSettings.setLastRunPaused(false);
         mActivityPaused = false;
         Tab current = mTabControl.getCurrentTab();
@@ -2404,6 +2401,7 @@
                 })
                 .show();
     }
+
     @Override
     public void showPageInfo() {
         mPageDialogsHandler.showPageInfo(mTabControl.getCurrentTab(), false, null);
diff --git a/src/com/android/browser/PreferenceKeys.java b/src/com/android/browser/PreferenceKeys.java
index d4280a6..0cc635a 100644
--- a/src/com/android/browser/PreferenceKeys.java
+++ b/src/com/android/browser/PreferenceKeys.java
@@ -132,6 +132,7 @@
     static final String PREF_USER_AGENT = "user_agent";
     static final String PREF_HELP = "help_about";
     static final String PREF_FEEDBACK = "feedback";
+    static final String PREF_AUTO_UPDATE = "update_notification";
     static final String PREF_EDGE_SWIPE = "edge_swiping_action";
     static final String PREF_LEGAL = "legal";
 
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();
+    }
+
+}
diff --git a/src/com/android/browser/preferences/AboutPreferencesFragment.java b/src/com/android/browser/preferences/AboutPreferencesFragment.java
index 2a0edac..7d14320 100644
--- a/src/com/android/browser/preferences/AboutPreferencesFragment.java
+++ b/src/com/android/browser/preferences/AboutPreferencesFragment.java
@@ -38,12 +38,14 @@
 import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceFragment;
 import android.preference.PreferenceScreen;
+import android.provider.Browser;
 
 import com.android.browser.BrowserActivity;
 import com.android.browser.BrowserPreferencesPage;
 import com.android.browser.BrowserSwitches;
 import com.android.browser.PreferenceKeys;
 import com.android.browser.R;
+import com.android.browser.UpdateNotificationService;
 
 import org.codeaurora.swe.BrowserCommandLine;
 
@@ -153,6 +155,18 @@
         setOnClickListener(PreferenceKeys.PREF_FEEDBACK, !mFeedbackRecipient.isEmpty());
 
         setOnClickListener(PreferenceKeys.PREF_LEGAL, true);
+        if (BrowserCommandLine.hasSwitch(BrowserSwitches.AUTO_UPDATE_SERVER_CMD)) {
+            setPreference(PreferenceKeys.PREF_AUTO_UPDATE,
+                    UpdateNotificationService.getLatestVersion(getActivity()));
+            setOnClickListener(PreferenceKeys.PREF_AUTO_UPDATE,
+                    UpdateNotificationService.getCurrentVersionCode(getActivity()) <
+                            UpdateNotificationService.getLatestVersionCode(getActivity()));
+        } else {
+            Preference pref = findPreference(PreferenceKeys.PREF_AUTO_UPDATE);
+            if (mHeadPref != null)
+                mHeadPref.removePreference(pref);
+        }
+
     }
 
     @Override
@@ -200,6 +214,14 @@
             intent.putExtra(Intent.EXTRA_TEXT, message);
             startActivity(Intent.createChooser(intent, "Select email application"));
             return true;
+        } else if (preference.getKey().equals(PreferenceKeys.PREF_AUTO_UPDATE)) {
+            Intent intent = new Intent(getActivity(), BrowserActivity.class);
+            intent.setAction(Intent.ACTION_VIEW);
+            intent.putExtra(Browser.EXTRA_APPLICATION_ID, getActivity().getPackageName());
+            intent.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true);
+            intent.setData(Uri.parse(
+                    UpdateNotificationService.getLatestDownloadUrl(getActivity())));
+            getActivity().startActivity(intent);
         }
         return false;
     }