com.theaetetuslabs.android_apkmaker.InstallActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.theaetetuslabs.android_apkmaker.InstallActivity.java

Source

/*
 * Copyright (C) 2016 B. Clint Hall
 *
 * 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.theaetetuslabs.android_apkmaker;

import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings.Secure;
import android.provider.Settings.SettingNotFoundException;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import com.theaetetuslabs.java_apkmaker.Logger;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

public class InstallActivity extends AppCompatActivity {
    private MoveApkTask moveApkTask;
    AlertDialog afterInstallDialog;
    AlertDialog tryAgainDialog;
    BuildFiles files;
    public static boolean verbose = true;

    static void setNeedStartInstall(boolean needStartInstall) {
        InstallActivity.needStartInstall = needStartInstall;
    }

    private static boolean needStartInstall = false;
    static final int INSTALL_REQUEST = 1;

    protected void onInstallError() {
        //Error.  probably caused by TopDogCheck deleting APK before user
        //tried to install it.
        if (tryAgainDialog != null)
            tryAgainDialog.cancel();
        new AlertDialog.Builder(this).setMessage(R.string.retry_install)
                .setPositiveButton(R.string.retry, new OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        moveApk();
                    }
                }).setNegativeButton(android.R.string.cancel, new OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        BuildFiles.deleteFiles(files.signed, files.extApk);
                        finish();
                    }
                }).setCancelable(false).show();
    }

    protected void onInstallSuccess() {
        if (afterInstallDialog != null)
            afterInstallDialog.cancel();
        AlertDialog.Builder builder = new AlertDialog.Builder(this)
                .setTitle(getStringReplacing(R.string.installed, "{{appName}}", AndroidApkMaker.newAppName))
                .setPositiveButton(getString(R.string.open), new OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Intent intent = getPackageManager()
                                .getLaunchIntentForPackage(AndroidApkMaker.newAppPackage);
                        if (intent != null) {
                            startActivity(intent);
                            finish();
                        }
                    }
                }).setNegativeButton(R.string.done, new OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        finish();
                    }
                }).setCancelable(false);
        if (AndroidApkMaker.adder != null) {
            AndroidApkMaker.adder.addToAfterInstallDialog(builder, this);
        }
        afterInstallDialog = builder.show();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Logger.logd("ResultCode: " + resultCode + "; requestCode: " + requestCode, verbose, System.out);
        if (requestCode == INSTALL_REQUEST) {
            BuildFiles.deleteFiles(files.extApk);
            if (resultCode != 1) {
                BuildFiles.deleteFiles(files.signed);
            }
            if (resultCode == 1) {
                onInstallError();
            } else if (resultCode == -1) {
                onInstallSuccess();
            } else {
                finish();
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d("TAG", "============Install Activity Created");

        setContentView(R.layout.activity_install);
        files = new BuildFiles(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (afterInstallDialog != null)
            afterInstallDialog.cancel();
        if (tryAgainDialog != null)
            tryAgainDialog.cancel();
    }

    @Override
    protected void onResume() {
        super.onResume();
        Logger.logd("InstallActivity#onResume. signed path: " + files.signed.getAbsolutePath(), verbose,
                System.out);
        if (needStartInstall) {
            SideLoadChecker slc = new SideLoadChecker(this);

            if (!files.signed.exists()) {
                finish();
                return;
            }
            if (!slc.canSideLoad()) {
                slc.requestSideLoad();
                return;
            }
            needStartInstall = false;
            if (Build.VERSION.SDK_INT >= 24) {
                //can install from uri: 24
                //cannot install from uri: 23, 22, 19 or 16
                tryInstallInternal();
            } else {
                Timer timer = new Timer();
                timer.schedule(new TopDogCheck(timer, this), 100);
                moveApk();
            }
        }
    }

    private String getStringReplacing(int id, String oldStr, String newStr) {
        return getString(id).replace(oldStr, newStr);
    }

    private Intent getInstallIntent() {
        Intent promptInstall = new Intent(Intent.ACTION_INSTALL_PACKAGE);
        promptInstall.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        promptInstall.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
        promptInstall.putExtra(Intent.EXTRA_RETURN_RESULT, true);
        return promptInstall;
    }

    private void tryInstallInternal() {
        Log.d("TAG", "signed exists? " + files.signed.exists() + ", " + files.signed.getAbsolutePath());
        System.out.println(BuildConfig.APPLICATION_ID + ".apkmakerfileprovider");
        Uri contentUri = FileProvider.getUriForFile(this, getPackageName() + ".apkmakerfileprovider", files.signed);
        grantUriPermission("com.android.packageinstaller", contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
        Intent promptInstall = getInstallIntent();
        promptInstall.setData(contentUri);
        List<ResolveInfo> list = getPackageManager().queryIntentActivities(promptInstall,
                PackageManager.MATCH_DEFAULT_ONLY);
        if (list.size() > 0) {
            startActivityForResult(promptInstall, INSTALL_REQUEST);
            Log.d("TAG", "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^Handled content URI");
        }

    }

    private void tryExternalInstall() {
        Log.d("TAG", "extApk exits? " + files.extApk.exists() + ", " + files.extApk.getAbsolutePath());
        if (files.extApk.exists()) {
            Intent promptInstall = getInstallIntent();
            Log.d("TAG", "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^NO NO NO NO NOT Handled content URI");
            promptInstall.setData(Uri.fromFile(files.extApk));
            startActivityForResult(promptInstall, INSTALL_REQUEST);
        }
    }

    private void moveApk() {
        if (moveApkTask == null) {
            moveApkTask = new MoveApkTask();
            moveApkTask.execute();
        }
    }

    private class MoveApkTask extends AsyncTask<Void, Void, Boolean> {
        void copy(File src, File dst) throws IOException {
            if (!dst.exists()) {
                dst.createNewFile();
            }
            InputStream in = new FileInputStream(src);
            OutputStream out = new FileOutputStream(dst);

            // Transfer bytes from in to out
            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
            in.close();
            out.close();
        }

        @Override
        protected Boolean doInBackground(Void... params) {
            BuildFiles files = new BuildFiles(InstallActivity.this);
            try {
                copy(files.signed, files.extApk);
                return true;
            } catch (IOException e) {
                e.printStackTrace();
            }
            return false;
        }

        @Override
        protected void onPostExecute(Boolean aBoolean) {
            super.onPostExecute(aBoolean);
            if (aBoolean) {
                tryExternalInstall();
            } else {
                BuildFiles.deleteFiles(files.extApk);
            }
            moveApkTask = null;
        }
    }

    public static class SideLoadChecker {
        private Context context;

        public SideLoadChecker(Context context) {
            this.context = context;
        }

        public void check() {
            if (canSideLoad()) {
                return;
            } else {
                requestSideLoad();
            }
        }

        public boolean canSideLoad() {
            try {
                int installNonMarketApps = Secure.getInt(context.getContentResolver(),
                        Secure.INSTALL_NON_MARKET_APPS);
                Logger.logd("installNonMarketApps: " + installNonMarketApps, verbose, System.out);
                return 1 == installNonMarketApps;
            } catch (SettingNotFoundException e) {
                e.printStackTrace();
            }
            return true;
        }

        public void requestSideLoad() {
            AlertDialog.Builder builder = new AlertDialog.Builder(context);
            builder.setTitle(R.string.unknown_sources_title);
            builder.setMessage(R.string.unknown_sources_text);
            builder.setNegativeButton(android.R.string.cancel, new OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                }
            });

            builder.setPositiveButton(android.R.string.ok, new OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    context.startActivity(new Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS));
                }
            });
            builder.show();
        }
    }

    public static boolean canAccessStorage(Context context) {
        BuildFiles files = new BuildFiles(context);
        if (files.extApk.exists()) {
            files.extApk.delete();
        }
        try {
            files.extApk.createNewFile();
            files.extApk.delete();
            return true;
        } catch (IOException e) {
            Logger.logd(files.extApk.getAbsolutePath(), verbose, System.out);
            return false;
        }
    }

    public static boolean hasEnoughSpace(Context context) {
        BuildFiles files = new BuildFiles(context);
        try {
            files.extApk.createNewFile();
            long free = files.extApk.getFreeSpace();
            Logger.logd("freespace: " + free, verbose, System.out);
            return free / 1024 > 5;
        } catch (IOException e) {
            return false;
        }
    }

    static class TopDogCheck extends TimerTask {
        Timer timer;
        WeakReference<Activity> activityRef;
        int myTaskId;
        private static int interval = 1000;
        BuildFiles files;

        TopDogCheck(Timer timer, Activity activity) {
            files = new BuildFiles(activity);
            this.timer = timer;
            activityRef = new WeakReference<Activity>(activity);
            ActivityManager activityManager = (ActivityManager) activity.getBaseContext()
                    .getSystemService(Activity.ACTIVITY_SERVICE);
            List<RunningTaskInfo> list = activityManager.getRunningTasks(1);
            myTaskId = list.get(0).id;
            Logger.logd("Self check started. Task id: " + myTaskId, verbose, System.out);
        }

        private TopDogCheck(Timer timer, Activity activity, int myTaskId) {
            files = new BuildFiles(activity);
            this.timer = timer;
            this.myTaskId = myTaskId;
            activityRef = new WeakReference<Activity>(activity);
            ActivityManager activityManager = (ActivityManager) activity.getBaseContext()
                    .getSystemService(Activity.ACTIVITY_SERVICE);
            List<ActivityManager.RunningTaskInfo> list = activityManager.getRunningTasks(1);
            int topTaskId = list.get(0).id;
            Logger.logd("Still top dog: " + (myTaskId == topTaskId), verbose, System.out);
            if (myTaskId != topTaskId) {
                files.deleteFiles(files.extApk);
                activityRef.clear();
            }
        }

        @Override
        public void run() {
            Activity activity = activityRef.get();
            if (activity != null) {
                if (files.extApk.exists()) {
                    timer.schedule(new TopDogCheck(timer, activity, myTaskId), interval);
                    timer = null;
                }
            }
        }
    }
}