How I hacked around the Pocket app to cut down on my browser tabs.

I’ve been an app developer for a bit now and I had never published a side project app on my own. So I figured I’d change that! I wanted to solve a problem I had of opening too many tabs in my browser on my phone. Recently I was on a plane and had over 50 browser tabs that wouldn’t load in airplane mode and was frustrated that I hadn’t remember to save any of them to Pocket. I had a bad habit of opening links to read later but not saving them. How could I make it easier to save links to Pocket?

What I wanted was an app that could behave like a browser. I could open links from the Twitter, Facebook and Hacker News apps and then immediately share to the Pocket app. I disliked the current flow of saving links to Pocket:

  1. Click on a link in the Twitter app
  2. Wait for the Chrome app to pop up
  3. Click the option menu
  4. Hit the share button
  5. Wait for the share sheet to finally finish loading
  6. Look for “Pocket”, but remember that the text is “Add to Pocket”, not “Pocket”
  7. Scroll back up the share sheet
  8. Go to click on the Pocket icon
  9. Mysteriously and (seemingly at random) the share sheet loads a top part and I almost click the wrong icon
  10. Finally scroll back down and click the Pocket icon

Gif of app loading and then another button popping up in place of another button

Actual gif of the share sheet loading in Android

I was also inspired to build the app after using Firefox Mobile’s feature to launch tabs in the background when clicking on links. The Pocket & Firefox apps show an Activity without a layout file, instead flashing some kind of modified Toast, so I figured I could make an app do the same to solve my problem.

First thing I did was try to find the activity in Pocket that saved an Article without a View. I used the APK Analyzer in Android Studio to look at Pocket’s AndroidManifest and see the list of activities. AddActivity stood out since it had a bunch of intent filters that would receive another app’s share intents. A quick google search confirmed that this was the Activity I wanted.

Now I could have used ADB to fire an intent to Pocket’s AddActivity to test it out, but I was itching to just build the app.

I knew the first activity had to do a few things:

  1. Receive intents that browser would usually open
  2. Not show any UI when the activity started,
  3. Open a new intent to Pocket’s AddActivity.

Thankfully, most of the magic is in my apps AndroidManifest. I created a BrowserInterceptActivity and gave it the following properties:

<activity
    android:name=".BrowserInterceptActivity"
    android:theme="@android:style/Theme.NoDisplay"
    android:noHistory="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />

        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />

        <data android:scheme="http" />
        <data android:scheme="https" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />

        <category android:name="android.intent.category.BROWSABLE" />
        <category android:name="android.intent.category.DEFAULT" />

        <data android:scheme="http" />
        <data android:scheme="https" />
        <data android:mimeType="text/html" />
        <data android:mimeType="text/plain" />
        <data android:mimeType="application/xhtml+xml" />
    </intent-filter>
</activity>

I shamelessly looked up what intent filters the Firefox Focus mobile app uses to make sure I had the correct ones. I think added android:noHistory="true because I was going to add a “About the app” activity and didn’t want to worry about this activity in the back stack. And android:theme="@android:style/Theme.NoDisplay" is there so that the Activity doesn’t inflate a View.

I was also itching to learn a bit of Kotlin. So I whipped up the following activity in Kotlin as a proof of concept.

class BrowserInterceptActivity : Activity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val action = intent.action
        var dataString: String
        if (Intent.ACTION_VIEW.equals(action)) {
            dataString = intent.dataString

            if (TextUtils.isEmpty(dataString)) {
                showError()
                return
            } else {
                openInPocket(dataString)
            }
        }

        finishAffinity()
    }

    private fun openInPocket(dataString: String) {
        val intent = Intent()
        intent.setClassName("com.ideashower.readitlater.pro", "com.ideashower.readitlater.activity.AddActivity")
        intent.putExtra(Intent.EXTRA_TEXT, dataString)

        if (isPocketAvailable(intent)) {
            startActivity(intent)
        } else {
            showError()
        }

        finish()
    }

    private fun isPocketAvailable(intent: Intent): Boolean {
        val packageManager = packageManager ?: return false
        val activities = packageManager.queryIntentActivities(intent, 0)

        return activities.size > 0
    }

    private fun showError() {
        Toast.makeText(this@BrowserInterceptActivity, "Error opening in Pocket", Toast.LENGTH_LONG).show()
    }
}

It’s pretty simple as you can see. There’s a tiny bit of error handling. When the activity opens, it checks the intent for a String, then opens the intent for Pocket’s AddActivity. You might also notice that I do a check to see if Pocket is installed— I added that after crashing the app in the emulator ?. Then the activity finishes with finishAffinity(). I used that instead of finish() because the app would also have an “About this app” activity and I didn’t want to have that Activity in the back stack.

That’s all I needed! I was super excited to load the APK on my phone and share on a few slacks. The app, which I’ve called Browser Interceptor looks a little something like this:

gif of app in use

If you want to check out the app you can download it from the play store. This app is also open sourced! View the source code on GitHub. PRs or Issues with any suggestions are welcome.

PS. Props to Alex Fu for launching Humemo and Zac Sweers for blogging about shipping Catch Up. Their projects’ prompted me to ship this side project and blog about it.

 
37
Kudos
 
37
Kudos

Now read this

Dealing with TimeZones in Java without Joda Time

I’ve begun working on a java SDK for nexmo’s API. Thanks to the wonders of timezones I had a crappy time (the 24 hour cold I currently have didn’t help). Mark works in the UK and I work on East Coast time. Only because of our team’s... Continue →