APK update outside of Google play, error: android.os.FileUriExposedException:

3

I have problems with updating the apk ; what I tried to do 1st download the information of a txt hosted in dropbox and compare the version data. If there is an update, you should download the file. The problem that the log reports to me is:

2018-10-29 16:36:38.491 2600-2641/santabeatriz.ventas4 E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #1
    Process: santabeatriz.ventas4, PID: 2600
    java.lang.RuntimeException: An error occurred while executing doInBackground()
        at android.os.AsyncTask$3.done(AsyncTask.java:318)
        at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:354)
        at java.util.concurrent.FutureTask.setException(FutureTask.java:223)
        at java.util.concurrent.FutureTask.run(FutureTask.java:242)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
        at java.lang.Thread.run(Thread.java:761)
     Caused by: android.os.FileUriExposedException: file:///storage/emulated/0/download/app.apk exposed beyond app through Intent.getData()
        at android.os.StrictMode.onFileUriExposed(StrictMode.java:1799)
        at android.net.Uri.checkFileUriExposed(Uri.java:2346)
        at android.content.Intent.prepareToLeaveProcess(Intent.java:8933)
        at android.content.Intent.prepareToLeaveProcess(Intent.java:8894)
        at android.app.Instrumentation.execStartActivity(Instrumentation.java:1517)
        at android.app.Activity.startActivityForResult(Activity.java:4224)
        at android.support.v4.app.BaseFragmentActivityJB.startActivityForResult(BaseFragmentActivityJB.java:48)
        at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:75)
        at android.app.Activity.startActivityForResult(Activity.java:4183)
        at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:856)
        at android.app.Activity.startActivity(Activity.java:4507)
        at android.app.Activity.startActivity(Activity.java:4475)
        at santabeatriz.ventas4.Autoupdater$2.doInBackground(Autoupdater.java:299)
        at santabeatriz.ventas4.Autoupdater$2.doInBackground(Autoupdater.java:262)
        at android.os.AsyncTask$2.call(AsyncTask.java:304)
        at java.util.concurrent.FutureTask.run(FutureTask.java:237)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:243) 
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) 
        at java.lang.Thread.run(Thread.java:761) 

My code is:

public class MainActivity extends AppCompatActivity {

    ConnectionClass connectionClass;
    EditText edtuserid,edtpass;
    Button btnlogin;
    ProgressBar pbbar;
    private DBHelper helper;
    private int ide;
    public final static String Valor1 = "";

    private Autoupdater updater;
    private RelativeLayout loadingPanel;
    private Context context;

    private static final int CODE_WRITE_SETTINGS_PERMISSION = 332;
    private static String[] PERMISSIONS_ALL = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}; //TODO You can Add multiple permissions here.
    private static final int PERMISSION_REQUEST_CODE = 223;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        try {
            loadingPanel = (RelativeLayout) findViewById(R.id.loadingPanel);
            //Esto sirve si la actualizacion no se realiza al principio. No es este caso.
            //loadingPanel.setVisibility(View.GONE);
            comenzarActualizar();

        }catch (Exception ex){
            //Por Cualquier error.
            Toast.makeText(this,ex.getMessage(),Toast.LENGTH_LONG);
        }


        helper = new DBHelper(this);
        connectionClass = new ConnectionClass();
        edtuserid = (EditText) findViewById(R.id.edtuserid);
        edtpass = (EditText) findViewById(R.id.edtpass);
        btnlogin = (Button) findViewById(R.id.btnlogin);
        pbbar = (ProgressBar) findViewById(R.id.pbbar);
        pbbar.setVisibility(View.GONE);

        //obtener el usuario guardado
        SharedPreferences preferences = getSharedPreferences("preferencias", Context.MODE_PRIVATE);
        edtuserid.setText(preferences.getString("usuario", ""));
         if (edtuserid.getText().toString().isEmpty()){

        }else{
             edtpass.requestFocus();
         }

        edtpass.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View view, int i, KeyEvent keyEvent) {
                if ((keyEvent.getAction() == KeyEvent.ACTION_DOWN) &&
                        (i == KeyEvent.KEYCODE_ENTER)) {
                    // Perform action on key press
                    //Toast.makeText(HelloFormStuff.this, edittext.getText(), Toast.LENGTH_SHORT).show();
                    DoLogin doLogin = new DoLogin();
                    doLogin.execute("");
                    return true;
                }
                return false;
            }
        });

        btnlogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                DoLogin doLogin = new DoLogin();
                doLogin.execute("");
            }

        });
    }

    private void comenzarActualizar(){

        context = this;

        updater = new Autoupdater(this);

        loadingPanel.setVisibility(View.VISIBLE);

        updater.DownloadData(finishBackgroundDownload);
    }

    private Runnable finishBackgroundDownload = new Runnable() {
        @Override
        public void run() {

            loadingPanel.setVisibility(View.GONE);

            if(updater.isNewVersionAvailable()){


                String msj = "Nueva Version: " + updater.isNewVersionAvailable();
                msj += "\nCurrent Version: " + updater.getCurrentVersionName() + "(" + updater.getCurrentVersionCode() + ")";
                msj += "\nLastest Version: " + updater.getLatestVersionName() + "(" + updater.getLatestVersionCode() +")";
                msj += "\nDesea Actualizar?";
                //Crea ventana de alerta.
                AlertDialog.Builder dialog1 = new AlertDialog.Builder(context);
                dialog1.setMessage(msj);
                dialog1.setNegativeButton(R.string.cancel, null);
                //Establece el boton de Aceptar y que hacer si se selecciona.
                dialog1.setPositiveButton(R.string.accept, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        //Vuelve a poner el ProgressBar mientras se baja e instala.
                        loadingPanel.setVisibility(View.VISIBLE);
                        //Se ejecuta el Autoupdater con la orden de instalar. Se puede poner un listener o no

                        if (ContextCompat.checkSelfPermission(MainActivity.this,
                                Manifest.permission.READ_EXTERNAL_STORAGE)
                                != PackageManager.PERMISSION_GRANTED) {

                            if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                                    Manifest.permission.READ_EXTERNAL_STORAGE)) {

                            } else {

                                ActivityCompat.requestPermissions(MainActivity.this,
                                        new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                                        PERMISSION_REQUEST_CODE);
                            }
                        }


                    }
                });

                dialog1.show();

            }else{

                Log.d("No Hay actualizaciones", "error");
            }
        }
    };



    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case PERMISSION_REQUEST_CODE: {

                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    updater.InstallNewVersion(null);
                } else {

                    Log.e("Update", "Permiso denegado");
                }
                return;
            }

        }
    }

}

public class Autoupdater {

    Context context;

    Runnable listener;

    private static final String INFO_FILE ="https://dl.dropboxusercontent.com/xxx/version.txt"; 
    private int currentVersionCode;

    private String currentVersionName;

    private int latestVersionCode;

    private String latestVersionName;

    private String downloadURL;

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

    private void getData() {
        try{
            // Datos locales
            Log.d("AutoUpdater", "GetData");
            PackageInfo pckginfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
            currentVersionCode = pckginfo.versionCode;
            currentVersionName = pckginfo.versionName;

            // Datos remotos
            String data = downloadHttp(new URL(INFO_FILE));
            JSONObject json = new JSONObject(data);
            latestVersionCode = json.getInt("versionCode");
            latestVersionName = json.getString("versionName");
            downloadURL = json.getString("downloadURL");
            Log.d("AutoUpdate", "Datos obtenidos con éxito");
        }catch(JSONException e){
            Log.e("AutoUpdate", "Ha habido un error con el JSON", e);
        }catch(PackageManager.NameNotFoundException e){
            Log.e("AutoUpdate", "Ha habido un error con el packete :S", e);
        }catch(IOException e){
            Log.e("AutoUpdate", "Ha habido un error con la descarga", e);
        }
    }

    public boolean isNewVersionAvailable() {
        return getLatestVersionCode() > getCurrentVersionCode();
    }

    public int getCurrentVersionCode() {
        return currentVersionCode;
    }

    public String getCurrentVersionName() {
        return currentVersionName;
    }

    public int getLatestVersionCode() {
        return latestVersionCode;
    }

    public String getLatestVersionName() {
        return latestVersionName;
    }

    public String getDownloadURL() {
        return downloadURL;
    }

    private static String downloadHttp(URL url) throws IOException {
        // Codigo de coneccion, Irrelevante al tema.
        HttpURLConnection c = (HttpURLConnection)url.openConnection();
        c.setRequestMethod("GET");
        c.setReadTimeout(15 * 1000);
        c.setUseCaches(false);
        c.connect();
        BufferedReader reader = new BufferedReader(new InputStreamReader(c.getInputStream()));
        StringBuilder stringBuilder = new StringBuilder();
        String line;
        while((line = reader.readLine()) != null){
            stringBuilder.append(line + "\n");
        }
        return stringBuilder.toString();
    }

    public void DownloadData(Runnable OnFinishRunnable){
        //Guarda el listener.
        this.listener = OnFinishRunnable;
        //Ejecuta el AsyncTask para bajar los datos.
        Log.e("Update error!", "Comienza Descarga APK");
        downloaderData.execute();
    }

    public void InstallNewVersion(Runnable OnFinishRunnable){

        if(isNewVersionAvailable()){
            if(getDownloadURL() == "") return;
            listener = OnFinishRunnable;
            String params[] = {getDownloadURL()};
            downloadInstaller.execute(params);

        }
    }

    private AsyncTask downloaderData = new AsyncTask() {
        @Override
        protected Object doInBackground(Object[] objects) {
            //llama al metodo auxiliar que seteara todas las variables.
            getData();
            return null;
        }

        @Override
        protected void onPostExecute(Object o) {
            super.onPostExecute(o);

            if(listener != null)listener.run();
            listener = null;
        }
    };

    private AsyncTask<String, Integer, Intent> downloadInstaller = new AsyncTask<String, Integer, Intent>() {
        @Override
        protected Intent doInBackground(String... strings) {
            try {
                URL url = new URL(strings[0]);
                HttpURLConnection c = (HttpURLConnection) url.openConnection();
                c.setRequestMethod("GET");
                c.setDoOutput(true);
                c.connect();

                String PATH = Environment.getExternalStorageDirectory() + "/download/";
                File file = new File(PATH);
                // File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
                file.mkdirs();
                File outputFile = new File(file, "app.apk");
                FileOutputStream fos = new FileOutputStream(outputFile);

                InputStream is = c.getInputStream();

                byte[] buffer = new byte[1024];
                int len1 = 0;
                while ((len1 = is.read(buffer)) != -1) {
                    fos.write(buffer, 0, len1);
                }
                fos.close();
                is.close();//till here, it works fine - .apk is download to my sdcard in download file


                Intent intent = new Intent(Intent.ACTION_VIEW);

                intent.setDataAndType(Uri.fromFile(new File(Environment.getExternalStorageDirectory() + "/download/" + "app.apk")), "application/vnd.android.package-archive");

                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                context.startActivity(intent);

            } catch (IOException e) {

                Log.e("Update error!", e.getMessage());
            }

            return null;
        }

        @Override
        protected void onPostExecute(Intent intent) {
            super.onPostExecute(intent);
            if(listener != null)listener.run();
            listener = null;
        }
    };
}

The error appears in the public Autoupdate > > context.startActivity(intent); Checking the version of the file apk , works fine .. if you can guide me in what I'm wrong I'd appreciate it

    
asked by Carlosd 30.10.2018 в 01:34
source

2 answers

1

The error FileUriExposedException is generated from Android N (Android 7.0) m this if you are using the method Uri.fromFile() , in this case you are doing it in this line :

  intent.setDataAndType(Uri.fromFile(new File(Environment.getExternalStorageDirectory() + "/download/" + "app.apk")), "application/vnd.android.package-archive");

To solve this problem, you must validate if you are using an operating system greater than or equal to Android 7.0 and use the Uri.parse() method instead of Uri.fromFile() :

 if (Build.VERSION.SDK_INT >=  Build.VERSION_CODES.N) {
      intent.setDataAndType(Uri.parse(new File(Environment.getExternalStorageDirectory() + "/download/" + "app.apk")), "application/vnd.android.package-archive");
  } else{
      intent.setDataAndType(Uri.fromFile(new File(Environment.getExternalStorageDirectory() + "/download/" + "app.apk")), "application/vnd.android.package-archive");
  }
    
answered by 30.10.2018 / 16:25
source
1

If you analyze the log message it indicates that the cause of the error is that your application is throwing the exception android.os.FileUriExposedException .

This is because your targetSdkVersion >= 24 . In that case, you must use a class FileProvider to give access to a file or folder to other applications. For this, it is best to create your own class that inherits from FileProvider , so that it ensures that our FileProvider does not conflict with other FileProviders declared in the imports , more information here (in English).

The steps to replace the URI file:// with a URI content:// are:

  • Add a class that extends FileProvider

    public class GenericFileProvider extends FileProvider {}
    
  • Add a <provider> tag in the AndroidManifest.xml file under the <application> tag. Specify a unique authority for the android:authorities attribute to avoid conflicts, the imported dependencies must specify ${applicationId}.provider and other commonly used authorities.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    <application
        ...
        <provider
            android:name=".GenericFileProvider"
            android:authorities="${applicationId}.my.package.name.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"/>
        </provider>
    </application>
</manifest>
  • Create a provider_paths.xml file in the res/xml folder. (If the folder does not exist, it must be created). The content of this file is shown below. This describes that we want to share the access to the root folder of the external storage (path=".") with the name external_files .
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="."/>
</paths>
  • Then you must change the line of code where you build the URI of the file, to one that gets the URI using the FileProvider, in your case it would change this:

    intent.setDataAndType(Uri.fromFile(new File(Environment.getExternalStorageDirectory() + "/download/" + "app.apk")), "application/vnd.android.package-archive");
    

    to something like this:

    intent.setDataAndType(FileProvider.getUriForFile(context,  context.getApplicationContext().getPackageName() + ".my.package.name.provider", new File(Environment.getExternalStorageDirectory() + "/download/" + "app.apk")), "application/vnd.android.package-archive");
    
  • If you use an intent to make the system open the file, you must also add the following line of code:

    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    

More information and the full explanation behind this solution at file: // scheme is now not allowed to be connected with Intent on targetSdkVersion 24 (Android Nougat). And here is the solution (in English).

With this response from StackOverflow in English.

EDITING : For the default functionality it is not necessary to write a class that extends FileProvider , you can directly use the class android.support.v4.content.FileProvider . For more information read the documentation of that class.

    
answered by 30.10.2018 в 02:30