Receiving Event, if a file was downloaded/ added to download folder
Asked Answered
C

3

14

I would like to receive an event whenever a file was added to a specific folder, e.g. the download folder. To reach this I tried 3 different approaches without any success. The target devices are Android 15+. Do you have any experience with any of these 3 approaches and could help out with a working sample?

APPROACH 1 - FileObserver:

In a background service I add a recursive file observer for the top folder as described here. On Android 4/5 its working but on Android 6 no events are fired (known issue) On top of that it seems that on Android 4/5 the file observer is not reliable. At some point the stopWatching() method gets called and from then on no event will be received.

in onStartCommand(..) of the service:

    new MyFileObserver(Constants.DOWNLOAD_PATH, true).startWatching();

APPROACH 2 - Content Observer:

I tried to tweak the content observer for my use case (as described here), but I never receive any events.

in onStart of the service:

 getContentResolver().registerContentObserver( Uri.parse("content://download/"), true,
            new MyObserver(myHandler));

.

public class MyObserver extends ContentObserver {
  // left blank below constructor for this Contact observer example to work
  // or if you want to make this work using Handler then change below registering  //line
  public MyObserver(Handler handler) {
    super(handler);
  }

  @Override
  public void onChange(boolean selfChange) {
    this.onChange(selfChange, null);
    Log.e("", "~~~~~~ on change" + selfChange);
    // Override this method to listen to any changes
  }

  @Override
  public void onChange(boolean selfChange, Uri uri) {
    // depending on the handler you might be on the UI
    // thread, so be cautious!
    Log.e("", "~~~~~~ on change uri" + selfChange);
  }
}

APPROACH 3 - BroadcastReceiver:

With a BroadcastReceiver I try to get the ON_DOWNLOAD_COMPLETE_EVENT (as described here. But nothing happens.

in on StartCommand(...) of the service:

 registerReceiver(new DownloadListenerService(), new IntentFilter(
            DownloadManager.ACTION_DOWNLOAD_COMPLETE));

DownloadListenerService:

public class DownloadListenerService extends BroadcastReceiver {
    @Override
    public void onReceive(final Context context, Intent intent) {
        System.out.println("got here");
        SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
        SharedPreferences.Editor editor = settings.edit();

        String action = intent.getAction();
        if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
            String downloadPath = intent.getStringExtra(DownloadManager.COLUMN_URI);
            editor.putString("downloadPath", downloadPath);
            editor.commit();
        }
    }
}

Manifest:

  <receiver
            android:name=".DownloadListenerService"
            android:icon="@mipmap/ic_launcher"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
            </intent-filter>
        </receiver>
Cufic answered 4/4, 2016 at 10:48 Comment(2)
How about this docs.oracle.com/javase/tutorial/essential/io/notification.html Its a java/linux solution. Under the covers that is what you are working withRobbegrillet
According to code.google.com/p/android/issues/detail?id=189231#c17 the bug has been solved. But theren't a workaround for the current Android versions :(Oligosaccharide
S
2

Actually, right now there is no known work-around for Android 6.0 and above. In Android 4/5/5.1 the FileObserver is working fine mostly, but for Android 6 you simply can't get any kind of response from the system when a file is added to an external directory. Knowing this FileObserver is completely useless in Android 6.

But eventually you can detect added content in the Android system, with the Content Observer which is also working fine in Android 6. Maybe this can solve your problem until Google provides a fix.

This is how I currently use the ContenObserver:

mycontentobserver = new MyContentObserver(handler,**Your path**,this);
getContentResolver().registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, mycontentobserver);

An than the MyContentOberserver.class I just check for last edited files in my specific path and if they are not older than 5-10 seconds I assume that this triggered the ContentObserver Event.

UPDATE EDIT:

This is how it should work for you:

In your BackgroundService.class:

mycontentobserver = new MyContentObserver(handler,**Your download folder path**,this);
getContentResolver().registerContentObserver(MediaStore.Files.getContentUri("external"), true, mycontentobserver);

And than inside the MyContentObserver.class:

public MyContentObserver(Handler handler, String workpath,  ContentModificationService workcontext) {
    super(handler);
    downloadfolderpath = workpath;
    contentcontext = workcontext;
}

@Override
public void onChange(boolean selfChange, Uri uri) {

   if(downloadfolderpath != null) {
      File file = new File(downloadfolder);
      if (file.isDirectory()) {
         listFile = file.listFiles();
         if (listFile != null && listFile.length > 0) {
            
            // Sort files from newest to oldest (this is not the best Method to do it, but a quick on)
            Arrays.sort(listFile, new Comparator<File>() {
               public int compare(File f1, File f2) {
               return      Long.valueOf(f1.lastModified()).compareTo(f2.lastModified());
               }
            });

            if (listFile[listFile.length - 1].lastModified() >= System.currentTimeMillis() - 5000) { //adjust the time (5000 = 5 seconds) if you want.

               //Some file was added !! Yeah!
               //Use the contentcontext to launch some Method in you Service
               //For example:
               contentcontext.SomeMethodToContinue();
            }
         }
      }
   }
}

I hope this helps, let me now If it works for you. It work's for me with the download folder on android 6.1 :)

Sprung answered 4/4, 2016 at 11:55 Comment(3)
Can you provide a working sample for the ContentObserver?Cufic
Actually It's not working on my device(Android 6.1). To watch the downloads folder I used this registration: context.getContentResolver().registerContentObserver(Uri.fromFile(new File(Environment.getExternalStorageDirectory().getAbsolutePath()+ "/Download/")),true,myObserver);Cufic
@Cufic Sorry, for not replying so long. ContentObserver is currently not triggering events for specific file paths. Try to use the following: MediaStore.Files.getContentUri("external"), this works. I'll update my post with a more precise solution for you.Sprung
C
0

I have tried your APPROACH 3-BroadcastReceiver in my android Application and it works for me. Please go through the code it may help you.You can change the image url according to your requirements.

My Activity and Broadcast receiver class:

package com.example.sudiproy.downloadingpath;

import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.google.gson.Gson;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    private DownloadManager downloadManager;
    private Button startDownload;
    private Button checkStatusOfDownload;
    private long downloadReference;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startDownload = (Button) findViewById(R.id.btn_download);
        checkStatusOfDownload = (Button) findViewById(R.id.btn_check_status);
        startDownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
                Uri Download_Uri = Uri.parse("http://demo.mysamplecode.com/Sencha_Touch/CountryServlet?start=0&limit=999");
                DownloadManager.Request request = new DownloadManager.Request(Download_Uri);
                //Set title to be displayed if notification is enabled
                request.setTitle("My Data Download");
                //Set the local destination for the downloaded file to a path within the application's external files directory
                request.setDestinationInExternalFilesDir(MainActivity.this, Environment.DIRECTORY_DOWNLOADS, "CountryList.json");
                //Enqueue a new download and same the referenceId
                downloadReference = downloadManager.enqueue(request);

                IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
                registerReceiver(downloadReceiver, filter);
            }
        });
        checkStatusOfDownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                DownloadManager.Query myDownloadQuery = new DownloadManager.Query();
                myDownloadQuery.setFilterById(downloadReference);
                //Query the download manager about downloads that have been requested.
                Cursor cursor = downloadManager.query(myDownloadQuery);
                if (cursor.moveToFirst()) {
                    checkStatus(cursor);
                }

            }
        });


    }

    private void checkStatus(Cursor cursor) {
        int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
        int status = cursor.getInt(columnIndex);
        //column for reason code if the download failed or paused
        int columnReason = cursor.getColumnIndex(DownloadManager.COLUMN_REASON);
        int reason = cursor.getInt(columnReason);
        //get the download filename
        int filenameIndex = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME);
        String filename = cursor.getString(filenameIndex);

        String statusText = "";
        String reasonText = "";


        switch (status) {
            case DownloadManager.STATUS_FAILED:
                statusText = "STATUS FAILED";
                break;
            case DownloadManager.STATUS_PAUSED:
                statusText = "STATUS_PAUSED";
                break;
            case DownloadManager.STATUS_PENDING:
                statusText = "STATUS_PENDING";
                break;
            case DownloadManager.STATUS_RUNNING:
                statusText = "STATUS_RUNNING";
                break;
            case DownloadManager.STATUS_SUCCESSFUL:
                statusText = "STATUS_SUCCESSFUL";
                reasonText = "Filename:\n" + filename;
                break;
        }
    }


    private BroadcastReceiver downloadReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            long referenceId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
            if (downloadReference == referenceId) {
                int ch;
                ParcelFileDescriptor file;
                StringBuffer strContent = new StringBuffer("");
                StringBuffer countryData = new StringBuffer("");
                try {
                    file = downloadManager.openDownloadedFile(downloadReference);
                    FileInputStream fileInputStream
                            = new ParcelFileDescriptor.AutoCloseInputStream(file);

                    while ((ch = fileInputStream.read()) != -1)
                        strContent.append((char) ch);

                    JSONObject responseObj = new JSONObject(strContent.toString());
                    JSONArray countriesObj = responseObj.getJSONArray("countries");

                    for (int i = 0; i < countriesObj.length(); i++) {
                        Gson gson = new Gson();
                        String countryInfo = countriesObj.getJSONObject(i).toString();
                        Country country = gson.fromJson(countryInfo, Country.class);
                        countryData.append(country.getCode() + ": " + country.getName() + "\n");
                    }
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (JSONException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }


            }
        }
    };
}

My Pojo class:

package com.example.sudiproy.downloadingpath;


public class Country {

    String code = null;
    String name = null;

    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

}

My Manifest Declaration:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.sudiproy.downloadingpath">
    <uses-permission android:name="android.permission.INTERNET">
    </uses-permission>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE">
        </uses-permission>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>


    </application>

</manifest>
Constituent answered 19/4, 2016 at 7:39 Comment(2)
I see you download inside your app. I wanted to get the event when the user downloads for example from the browser.Cufic
Yes,it is also downloading from the browser only.If you want you can provide your own url to download it.Constituent
B
0

Your 3rd approach is the best one and the most efficient one. However try using the full package name in your receiver tag in the Manifest. The problem is that the action is sent to a receiver that is not found. For example:

<receiver
    android:name="com.example.mypackage.receivers.DownloadListenerService"
    android:icon="@mipmap/ic_launcher"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
    </intent-filter>
</receiver>
Bucktooth answered 21/4, 2016 at 3:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.