pct.droid.base.updater.PopcornUpdater.java Source code

Java tutorial

Introduction

Here is the source code for pct.droid.base.updater.PopcornUpdater.java

Source

/*
 * This file is part of Popcorn Time.
 *
 * Popcorn Time is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Popcorn Time is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Popcorn Time. If not, see <http://www.gnu.org/licenses/>.
 */

package pct.droid.base.updater;

import android.annotation.TargetApi;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.support.v4.app.NotificationCompat;

import com.google.gson.Gson;
import com.squareup.okhttp.Callback;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.Locale;
import java.util.Map;
import java.util.Observable;
import java.util.zip.CRC32;
import java.util.zip.Checksum;

import pct.droid.base.BuildConfig;
import pct.droid.base.Constants;
import pct.droid.base.PopcornApplication;
import pct.droid.base.R;
import pct.droid.base.preferences.Prefs;
import pct.droid.base.utils.NetworkUtils;
import pct.droid.base.utils.PrefUtils;

public class PopcornUpdater extends Observable {

    private static PopcornUpdater sThis;

    private static int NOTIFICATION_ID = 0xDEADBEEF;

    public final String STATUS_CHECKING = "checking_updates";
    public final String STATUS_NO_UPDATE = "no_updates";
    public final String STATUS_GOT_UPDATE = "got_update";
    public final String STATUS_HAVE_UPDATE = "have_update";

    private final long MINUTES = 60 * 1000;
    private final long HOURS = 60 * MINUTES;
    private final long DAYS = 24 * HOURS;
    private final long WAKEUP_INTERVAL = 15 * MINUTES;
    private long UPDATE_INTERVAL = 3 * HOURS;

    private final String ANDROID_PACKAGE = "application/vnd.android.package-archive";
    private final String DATA_URL = "http://ci.popcorntime.io/android";

    public static final String LAST_UPDATE_CHECK = "update_check";
    private static final String LAST_UPDATE_KEY = "last_update";
    private static final String UPDATE_FILE = "update_file";
    private static final String SHA1_TIME = "sha1_update_time";
    private static final String SHA1_KEY = "sha1_update";

    private final OkHttpClient mHttpClient = PopcornApplication.getHttpClient();
    private final Gson mGson = new Gson();
    private final Handler mUpdateHandler = new Handler();

    private Context mContext = null;
    private long lastUpdate = 0;
    private String mPackageName;
    private String mVersionName;
    private Integer mVersionCode;

    private PopcornUpdater(Context context) {
        if (Constants.DEBUG_ENABLED) {
            UPDATE_INTERVAL = 3 * HOURS;
        } else {
            UPDATE_INTERVAL = 2 * DAYS;
        }

        mContext = context;
        mPackageName = context.getPackageName();

        try {
            PackageInfo pInfo = context.getPackageManager().getPackageInfo(mPackageName, 0);
            mVersionCode = pInfo.versionCode;
            mVersionName = pInfo.versionName;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        lastUpdate = PrefUtils.get(mContext, LAST_UPDATE_KEY, 0l);
        NOTIFICATION_ID += crc32(mPackageName);

        ApplicationInfo appinfo = context.getApplicationInfo();

        if (new File(appinfo.sourceDir).lastModified() > PrefUtils.get(mContext, SHA1_TIME, 0l)) {
            PrefUtils.save(mContext, SHA1_KEY, SHA1(appinfo.sourceDir));
            PrefUtils.save(mContext, SHA1_TIME, System.currentTimeMillis());

            String update_file = PrefUtils.get(mContext, UPDATE_FILE, "");
            if (update_file.length() > 0) {
                if (new File(context.getFilesDir().getAbsolutePath() + "/" + update_file).delete()) {
                    PrefUtils.remove(mContext, UPDATE_FILE);
                }
            }
        } else {
            sendNotification();
        }

        context.registerReceiver(mConnectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
    }

    public static PopcornUpdater getInstance(Context context) {
        if (sThis == null) {
            sThis = new PopcornUpdater(context);
        }
        sThis.mContext = context;
        return sThis;
    }

    private Runnable periodicUpdate = new Runnable() {
        @Override
        public void run() {
            checkUpdates(false);
            mUpdateHandler.removeCallbacks(periodicUpdate);
            mUpdateHandler.postDelayed(this, WAKEUP_INTERVAL);
        }
    };

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public void checkUpdates(boolean forced) {
        long now = System.currentTimeMillis();

        if (!PrefUtils.get(mContext, Prefs.AUTOMATIC_UPDATES, true) && !forced) {
            return;
        }

        PrefUtils.save(mContext, LAST_UPDATE_CHECK, now);

        if (forced || (lastUpdate + UPDATE_INTERVAL) < now) {
            lastUpdate = System.currentTimeMillis();
            PrefUtils.save(mContext, LAST_UPDATE_KEY, lastUpdate);
            setChanged();

            if (BuildConfig.GIT_BRANCH.contains("local"))
                return;

            notifyObservers(STATUS_CHECKING);

            final String abi;
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                abi = Build.CPU_ABI.toLowerCase(Locale.US);
            } else {
                abi = Build.SUPPORTED_ABIS[0].toLowerCase(Locale.US);
            }

            final String variantStr;
            if (mPackageName.contains("tv")) {
                variantStr = "tv";
            } else {
                variantStr = "mobile";
            }

            final String channelStr;
            if (BuildConfig.BUILD_TYPE.equals("release")) {
                channelStr = "release";
            } else {
                channelStr = BuildConfig.GIT_BRANCH;
            }

            Request request = new Request.Builder().url(DATA_URL + "/" + variantStr).build();

            mHttpClient.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Request request, IOException e) {
                    setChanged();
                    notifyObservers(STATUS_NO_UPDATE);
                }

                @Override
                public void onResponse(Response response) {
                    try {
                        if (response.isSuccessful()) {
                            UpdaterData data = mGson.fromJson(response.body().string(), UpdaterData.class);
                            Map<String, Map<String, UpdaterData.Arch>> variant;

                            if (variantStr.equals("tv")) {
                                variant = data.tv;
                            } else {
                                variant = data.mobile;
                            }

                            UpdaterData.Arch channel = null;
                            if (variant.containsKey(channelStr) && variant.get(channelStr).containsKey(abi)) {
                                channel = variant.get(channelStr).get(abi);
                            }

                            if (channel == null || channel.checksum.equals(PrefUtils.get(mContext, SHA1_KEY, "0"))
                                    || channel.versionCode <= mVersionCode) {
                                setChanged();
                                notifyObservers(STATUS_NO_UPDATE);
                            } else {
                                downloadFile(channel.updateUrl);
                                setChanged();
                                notifyObservers(STATUS_GOT_UPDATE);
                            }
                        } else {
                            setChanged();
                            notifyObservers(STATUS_NO_UPDATE);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

    private void downloadFile(final String location) {
        Request request = new Request.Builder().url(location).build();

        mHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Request request, IOException e) {
                setChanged();
                notifyObservers(STATUS_NO_UPDATE);
            }

            @Override
            public void onResponse(Response response) throws IOException {
                if (response.isSuccessful()) {
                    String fileName = location.substring(location.lastIndexOf('/') + 1);
                    FileOutputStream fos = mContext.openFileOutput(fileName, Context.MODE_WORLD_READABLE);
                    fos.write(response.body().bytes());
                    fos.close();

                    PrefUtils.save(mContext, UPDATE_FILE, fileName);

                    String update_file_path = mContext.getFilesDir().getAbsolutePath() + "/" + fileName;
                    PrefUtils.getPrefs(mContext).edit().putString(SHA1_KEY, SHA1(update_file_path))
                            .putString(UPDATE_FILE, fileName).putLong(SHA1_TIME, System.currentTimeMillis())
                            .apply();
                    sendNotification();

                    setChanged();
                    notifyObservers(STATUS_HAVE_UPDATE);
                } else {
                    setChanged();
                    notifyObservers(STATUS_NO_UPDATE);
                }
            }
        });
    }

    public void checkUpdatesManually() {
        checkUpdates(true);
    }

    private BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {

            // do application-specific task(s) based on the current network state, such
            // as enabling queuing of HTTP requests when currentNetworkInfo is connected etc.
            if (NetworkUtils.isWifiConnected(context)) {
                checkUpdates(false);
                mUpdateHandler.postDelayed(periodicUpdate, UPDATE_INTERVAL);
            } else {
                mUpdateHandler.removeCallbacks(periodicUpdate); // no network anyway
            }
        }
    };

    public void sendNotification() {
        NotificationManager nm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);

        String updateFile = PrefUtils.get(mContext, UPDATE_FILE, "");
        if (updateFile.length() > 0) {
            setChanged();
            notifyObservers(STATUS_HAVE_UPDATE);

            NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(mContext)
                    .setSmallIcon(R.drawable.ic_notif_logo)
                    .setContentTitle(mContext.getString(R.string.update_available))
                    .setContentText(mContext.getString(R.string.press_install)).setAutoCancel(true)
                    .setDefaults(NotificationCompat.DEFAULT_ALL);

            Intent notificationIntent = new Intent(Intent.ACTION_VIEW);
            notificationIntent.setDataAndType(
                    Uri.parse("file://" + mContext.getFilesDir().getAbsolutePath() + "/" + updateFile),
                    ANDROID_PACKAGE);

            notificationBuilder.setContentIntent(PendingIntent.getActivity(mContext, 0, notificationIntent, 0));

            nm.notify(NOTIFICATION_ID, notificationBuilder.build());
        } else {
            nm.cancel(NOTIFICATION_ID);
        }
    }

    private String SHA1(String filename) {
        final int BUFFER_SIZE = 8192;
        byte[] buf = new byte[BUFFER_SIZE];
        int length;
        try {
            FileInputStream fis = new FileInputStream(filename);
            BufferedInputStream bis = new BufferedInputStream(fis);
            MessageDigest md = MessageDigest.getInstance("SHA1");
            while ((length = bis.read(buf)) != -1) {
                md.update(buf, 0, length);
            }

            byte[] array = md.digest();
            StringBuilder sb = new StringBuilder();
            for (byte anArray : array) {
                sb.append(Integer.toHexString((anArray & 0xFF) | 0x100).substring(1, 3));
            }
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }

        return "sha1bad";
    }

    private static int crc32(String str) {
        byte bytes[] = str.getBytes();
        Checksum checksum = new CRC32();
        checksum.update(bytes, 0, bytes.length);
        return (int) checksum.getValue();
    }

}