Search code examples
javaandroidandroid-intentandroid-activityandroid-service

Sending intents from a worker to an activity in a separate app


I have an app that writes to its local storage depending on user actions; said contents need to be forwarded to another app.

My approach:

  • create a worker thread with a file observer pointed to local storage
  • start worker from the apps main activity
  • worker thread creates and sends intents with updated contents to separate app

I'm not sure (maybe need to open a separate question), but everything created in an activity gets destroyed when the activity is stopped, right? meaning that adding workers, file observers have the same life span as the activity they're defined in, right?

Code:

MainActivity.java:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getSimpleName();
    private static final String FILE_OBSERVER_WORK_NAME = "file_observer_work";

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

        Log.i(TAG, "Creating file observer worker");
        WorkManager workManager = WorkManager.getInstance(getApplication());

        WorkContinuation continuation = workManager
                .beginUniqueWork(FILE_OBSERVER_WORK_NAME,
                        ExistingWorkPolicy.REPLACE,
                        OneTimeWorkRequest.from(APIWorker.class));

        Log.i(TAG, "Starting worker");
        continuation.enqueue();

        final Button button = findViewById(R.id.button2);
        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                Log.i(TAG, "Button clicked!");

                String stuffToWriteToFile = getStuff();
                String cwd = getApplicationInfo().dataDir;
                String stuffFilePath= cwd + File.separator + "stuff.json";

                PrintWriter stuffFile= null;
                try {
                    stuffFile = new PrintWriter(stuffFilePath, "UTF-8");
                    stuffFile.println(stuffToWriteToFile);
                    stuffFile.close();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    public void onResume(){
        super.onResume();
        // start worker here?
    }

    @Override
    public void onStart() {
        super.onStart();
    // start worker here?
    }
}

APIWorker.java: public class APIWorker extends Worker {

    public APIWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    private static final String TAG = APIWorker.class.getSimpleName();

    @NonNull
    @Override
    public Result doWork() {
        Context applicationContext = getApplicationContext();
        Log.d(TAG, "Observing stuff file");

        FileObserver fileObserver = new FileObserver(cwd) {
            @Override
            public void onEvent(int event, @Nullable String path) {
                if(event == FileObserver.CREATE ||
                   event == FileObserver.MODIFY) {

                    String cwd = applicationContext.getApplicationInfo().dataDir;
                    String stuffFilePath = cwd + File.separator + "stuff.json";

                    String fileContents;
                    File observedFile = new File(stuffFilePath);
                    long length = observedFile.length();
                    if (length < 1 || length > Integer.MAX_VALUE) {
                        fileContents = "";
                        Log.w(TAG, "Empty file: " + observedFile);
                    } else {
                        try (FileReader in = new FileReader(observedFile)) {
                            char[] content = new char[(int)length];

                            int numRead = in.read(content);
                            if (numRead != length) {
                                Log.e(TAG, "Incomplete read of " + observedFile +
                                        ". Read chars " + numRead + " of " + length);
                            }
                            fileContents = new String(content, 0, numRead);

                            Log.d(TAG, "Sending intent ");
                            String packageName = "com.cam.differentapp";
                            Intent sendIntent = applicationContext.getPackageManager().
                                    getLaunchIntentForPackage(packageName);
                            if (sendIntent == null) {
                                // Bring user to the market or let them choose an app?
                                sendIntent = new Intent(Intent.ACTION_VIEW);
                                sendIntent.setData(Uri.parse("market://details?id=" + packageName));
                            }
                            // sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            sendIntent.setAction(Intent.ACTION_SEND);
                            sendIntent.putExtra(Intent.EXTRA_TEXT, fileContents);
                            sendIntent.setType("application/json");

                            applicationContext.startActivity(sendIntent);

                            Log.d(TAG, "Intent sent ");
                        }
                        catch (Exception ex) {
                            Log.e(TAG, "Failed to read file " + path, ex);
                            fileContents = "";
                        }
                    }
                }
            }
        };

        fileObserver.startWatching();

        return null;
    }
}

Looking at the docs:

https://developer.android.com/guide/components/activities/background-starts

there are restrictions as to when activities can be started from the background but also exceptions, namely:

The app has a visible window, such as an activity in the foreground.

meaning (I think?) that as long as the user interacts with the app (MainActivity) the background worker should run, correct? It's stopped if the activity is paused/destroyed, right?


Solution

  • Usually you would use a Service if you have background processing to do that doesn't need user interaction (display or user input). If your app is in the foreground then your Service can launch other activities using startActivity().

    Your architecture seems very strange to me. You are using a Worker, which has a maximum 10 minute lifetime. You are starting the Worker which then creates a FileObserver to detect creation/modification of files. It then reads the file and starts another Activity. This is a very complicated and roundabout way of doing things. I have doubts that you can get this working reliably.

    Your Activity is writing the data to the file system. It could just call a method (on a background thread) after it has written the file that then forwards the data to another Activity. This would be much more straightforward and has a lot less moving parts.

    I don't know exactly how the lifecycle of the Activity effects the Workers. I would assume that they are not directly linked to the Activity and therefore would not stop when the Activity is paused or destroyed.

    I also notice that you are writing to a file on the main (UI) thread (in your OnClickListener). This is not OK and you should do file I/O in a background thread, because file I/O can block and you don't want to block the main (UI) thread.