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:
- Click on a link in the Twitter app
- Wait for the Chrome app to pop up
- Click the option menu
- Hit the share button
- Wait for the share sheet to finally finish loading
- Look for “Pocket”, but remember that the text is “Add to Pocket”, not “Pocket”
- Scroll back up the share sheet
- Go to click on the Pocket icon
- Mysteriously and (seemingly at random) the share sheet loads a top part and I almost click the wrong icon
- Finally scroll back down and click the Pocket icon
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:
- Receive intents that browser would usually open
- Not show any UI when the activity started,
- 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:
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.