├── res ├── raw │ └── apps.yaml ├── drawable-hdpi │ └── u_turn.png ├── drawable-mdpi │ └── u_turn.png ├── drawable-xhdpi │ └── u_turn.png ├── drawable-xxhdpi │ └── u_turn.png ├── values │ └── strings.xml └── layout │ └── main.xml ├── yaml ├── screenshot_1.png ├── screenshot_2.png ├── u_turn_promo.png ├── u_turn_original.gif ├── u_turn_play_store.png ├── libs └── snakeyaml-1.10-android.jar ├── .gitignore ├── local.properties ├── project.properties ├── ant.properties ├── generate_manifest.py ├── src └── org │ └── snarfed │ └── android │ └── openinapp │ ├── TwitterWebIntent.java │ └── Handler.java ├── app_manifests ├── com.silentlabs.android.mobilequeue_AndroidManifest.xml ├── com.hulu.plus_AndroidManifest.xml ├── org.wordpress.android_AndroidManifest.xml ├── com.opentable_AndroidManifest.xml ├── com.goodreads_AndroidManifest.xml ├── com.instagram.android_AndroidManifest.xml ├── intent_filters ├── com.github.mobile_AndroidManifest.xml ├── com.android.calendar_AndroidManifest.xml ├── com.google.android.calendar_AndroidManifest.xml ├── com.twitter.android_AndroidManifest.xml └── com.google.android.apps.plus_AndroidManifest.xml ├── README.md ├── AndroidManifest.xml ├── test_links.html └── apps.yaml /res/raw/apps.yaml: -------------------------------------------------------------------------------- 1 | ../../apps.yaml -------------------------------------------------------------------------------- /yaml: -------------------------------------------------------------------------------- 1 | ../../google_appengine/lib/yaml/lib/yaml -------------------------------------------------------------------------------- /screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snarfed/open-in-app/HEAD/screenshot_1.png -------------------------------------------------------------------------------- /screenshot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snarfed/open-in-app/HEAD/screenshot_2.png -------------------------------------------------------------------------------- /u_turn_promo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snarfed/open-in-app/HEAD/u_turn_promo.png -------------------------------------------------------------------------------- /u_turn_original.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snarfed/open-in-app/HEAD/u_turn_original.gif -------------------------------------------------------------------------------- /u_turn_play_store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snarfed/open-in-app/HEAD/u_turn_play_store.png -------------------------------------------------------------------------------- /res/drawable-hdpi/u_turn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snarfed/open-in-app/HEAD/res/drawable-hdpi/u_turn.png -------------------------------------------------------------------------------- /res/drawable-mdpi/u_turn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snarfed/open-in-app/HEAD/res/drawable-mdpi/u_turn.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/u_turn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snarfed/open-in-app/HEAD/res/drawable-xhdpi/u_turn.png -------------------------------------------------------------------------------- /libs/snakeyaml-1.10-android.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snarfed/open-in-app/HEAD/libs/snakeyaml-1.10-android.jar -------------------------------------------------------------------------------- /res/drawable-xxhdpi/u_turn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snarfed/open-in-app/HEAD/res/drawable-xxhdpi/u_turn.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Package Files # 4 | *.jar 5 | *.war 6 | *.ear 7 | 8 | # Directories 9 | bin 10 | gen 11 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Open Link in App 4 | 5 | -------------------------------------------------------------------------------- /res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /local.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must *NOT* be checked into Version Control Systems, 5 | # as it contains information specific to your local configuration. 6 | 7 | # location of the SDK. This is only used by Ant 8 | # For customization when using a Version Control System, please read the 9 | # header note. 10 | sdk.dir=/Users/ryan/android-sdk 11 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-17 15 | -------------------------------------------------------------------------------- /ant.properties: -------------------------------------------------------------------------------- 1 | # This file is used to override default values used by the Ant build system. 2 | # 3 | # This file must be checked into Version Control Systems, as it is 4 | # integral to the build system of your project. 5 | 6 | # This file is only used by the Ant script. 7 | 8 | # You can use this to override default values such as 9 | # 'source.dir' for the location of your java source folder and 10 | # 'out.dir' for the location of your output folder. 11 | 12 | # You can also use it define how the release builds are signed by declaring 13 | # the following properties: 14 | # 'key.store' for the location of your keystore and 15 | # 'key.alias' for the name of the key to use. 16 | # The password will be asked during the build when you use the 'release' target. 17 | 18 | key.store=/Users/ryan/.keystore 19 | key.alias=ryan_barrett 20 | -------------------------------------------------------------------------------- /generate_manifest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Generates AndroidManifest.xml based on apps.yaml.""" 3 | 4 | import datetime 5 | import yaml 6 | 7 | CONFIG_FILE = 'apps.yaml' 8 | 9 | 10 | def main(): 11 | with open(CONFIG_FILE) as f: 12 | config = yaml.load(f) 13 | 14 | schemes = config['schemes'] 15 | data = [] 16 | for app in config['apps']: 17 | assert 'hosts' in app, 'app %s is missing hosts field' % app['name'] 18 | data_elements = [] 19 | 20 | for host in app['hosts']: 21 | for scheme in schemes: 22 | prefixes = app.get('prefixes', []) 23 | patterns = app.get('patterns', []) 24 | if not prefixes and not patterns: 25 | prefixes = ['/'] 26 | 27 | for tag, values in ('pathPrefix', prefixes), ('pathPattern', patterns): 28 | for value in values: 29 | # To catch links in Chrome, must include scheme, host, and 30 | # either pathPrefix or pathPattern. 31 | # http://stackoverflow.com/questions/17706667 32 | data.append( 33 | '' % 34 | (scheme, host, tag, value)) 35 | 36 | print """\ 37 | 38 | 41 | 45 | 46 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | %s 57 | 58 | 59 | 60 | 61 | 62 | """ % (datetime.datetime.now(), '\n'.join(data)) 63 | 64 | 65 | if __name__ == "__main__": 66 | main() 67 | -------------------------------------------------------------------------------- /src/org/snarfed/android/openinapp/TwitterWebIntent.java: -------------------------------------------------------------------------------- 1 | package org.snarfed.android.openinapp; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.net.Uri; 7 | import android.util.Log; 8 | 9 | // This activity is currently unused. The official Twitter app handles web 10 | // intent URLs, but it redirects them to the browser. It doesn't have native 11 | // intents for handling retweeting, favorites, etc. anyway. :/ 12 | 13 | // Test command line: adb -d shell am start -d [link] 14 | 15 | // test links from https://dev.twitter.com/docs/intents : 16 | // http://twitter.com/intent/tweet?url=https://twitter.com/intent/tweet?url=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FIn_Watermelon_Sugar&in_reply_to=62862594515546112&via=twicodeer&text=foo%20bar&hashtags=baz,baj 17 | // http://twitter.com/intent/favorite?tweet_id=62862594515546112 18 | // http://twitter.com/intent/retweet?tweet_id=62862594515546112 19 | // http://twitter.com/intent/user?screen_name=alwaysmikegomez 20 | 21 | public class TwitterWebIntent extends Activity { 22 | static final String TAG = "OpenLinkInApp.TwitterWebIntent"; 23 | 24 | @Override 25 | public void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | 28 | Uri uri = getIntent().getData(); 29 | if (uri == null || uri.getPath() == null) { 30 | Log.i(TAG, "No URI in intent! Exiting."); 31 | finish(); 32 | return; 33 | } 34 | 35 | // http://wiki.akosma.com/IPhone_URL_Schemes#Twitter 36 | // http://omgwtfgames.com/2012/01/android-intents-captured-by-various-twitter-clients/ 37 | Intent intent = null; 38 | if (uri.getPath().equals("/intent/tweet")) { 39 | intent = new Intent(Intent.ACTION_SEND); 40 | intent.setType("text/plain"); 41 | 42 | StringBuilder builder = new StringBuilder(); 43 | String text = uri.getQueryParameter("text"); 44 | if (text != null) { 45 | builder.append(text); 46 | } 47 | 48 | String hashtags = uri.getQueryParameter("hashtags"); 49 | if (hashtags != null) { 50 | for (String tag : hashtags.split(",")) { 51 | builder.append(" #" + tag); 52 | } 53 | } 54 | 55 | String url = uri.getQueryParameter("url"); 56 | if (url != null) { 57 | builder.append(url); 58 | } 59 | 60 | String via = uri.getQueryParameter("via"); 61 | if (via != null) { 62 | builder.append(" via @" + via); 63 | } 64 | 65 | intent.putExtra(Intent.EXTRA_TEXT, text); 66 | Log.i(TAG, "Redirecting " + uri + " to ACTION_SEND with text/plain: " + text); 67 | 68 | } else { 69 | intent = new Intent(getIntent()); 70 | Log.i(TAG, "Unknown path " + uri.getPath() + " , resending original intent."); 71 | } 72 | 73 | startActivity(intent); 74 | finish(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app_manifests/com.silentlabs.android.mobilequeue_AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Open Link in App 2 | ================ 3 | 4 | Ever click on a link to Facebook, Twitter, or anywhere else, and it opens in the 5 | browser instead of the native app? Me too. No fun. This app intercepts those 6 | links and sends them to their native app. 7 | 8 | (See the 9 | [Play Store listing](https://play.google.com/store/apps/details?id=org.snarfed.android.openinapp) 10 | and [blog post announcement](http://snarfed.org/2013-07-16_open-link-in-app).) 11 | 12 | To use, after you click on link, select Open Link in App from the chooser dialog 13 | box. If you don't see the dialog box on a link that you think should work, go to 14 | Settings => Apps => Browser or Chrome => Clear defaults. 15 | 16 | I'm not actively working on this right now, but if you're technical, it's pretty 17 | straightforward to add a new app, and I happily accept pull requests. 18 | 19 | License: this project is placed in the public domain. 20 | 21 | 22 | Related work 23 | === 24 | [Tasomaniac](http://www.tasomaniac.com)'s 25 | [Open Link With...](https://play.google.com/store/apps/details?id=com.tasomaniac.openwith) 26 | is similar, but only works when you explicitly share a URL. 27 | 28 | [Intrications](http://www.intrications.com)'s 29 | [Browser Intercept - Share URL](https://play.google.com/store/apps/details?id=com.intrications.android.sharebrowser) 30 | lets you share a text URL instead of opening it in a browser. 31 | 32 | And others... 33 | 34 | 35 | Development notes 36 | === 37 | 38 | This app is heavily data-driven. The external apps to integrate with are defined 39 | in apps.yaml. generate_manifest.py uses that file at compile time to generate 40 | AndroidManifest.xml, and the app reads it at runtime to determine how to handle 41 | and redirect intents. 42 | 43 | The Python YAML library is PyYAML, grossly hacked as a symlink to App Engine's 44 | version in ~/google_appengine/ so that I don't have to check it into the repo. 45 | The Java YAML library is SnakeYAML, checked into libs/ as a jar. 46 | 47 | Test command line to open URL with ACTION_VIEW intent: 48 | adb -d shell am start -d [link] 49 | 50 | Lots of apps' AndroidManifest.xml manifest files are in app_manifests/. 51 | To extract an app's manifest: 52 | - "Back up" the app with 53 | [Astro File Manager](https://play.google.com/store/apps/details?id=com.metago.astro) 54 | - adb pull the apk from /sdcard/backups/apps 55 | - extract AndroidManifest.xml with 56 | [apktool](http://code.google.com/p/android-apktool/): apktool decode FILE.apk 57 | 58 | For an intent filter to catch taps on links in Chrome for Android, you have to 59 | include scheme, host, *and* either pathPrefix or pathPattern in the intent 60 | filter's data element: http://stackoverflow.com/questions/17706667 61 | 62 | Oddly, this also seems to "unlock" other apps' intent filters for browser link 63 | taps too. Goodreads, for example, doesn't normally handle browser link taps, but 64 | it does when Open Link in App is installed. Odd. 65 | 66 | Unfortunately, pathPattern is a very limited subset of regexp: only . and * are 67 | supported. That's not enough for some of the URI pattern matching we need. In 68 | these cases, we overspecify a prefix or pattern and do the rest of the filtering 69 | at runtime. 70 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /test_links.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 16 | 17 |

Facebook

18 | 25 | 26 |

Twitter

27 | 33 | 34 |

Instagram

35 | 42 | 43 |

GitHub

44 |
  • http://github.com/snarfed
  • 45 |
  • http://www.github.com/snarfed/open-in-app
  • 46 |
  • http://github.com/snarfed/open-in-app/commit/a8865ac8d7bd13667287943a8b9e81b8eb970629
  • 47 |
  • http://github.com/snarfed/facebook-atom/issues/1
  • 48 |
  • https://github.com/rogerhu/mockfacebook/pull/22
  • 49 |
  • https://gist.github.com/6002797
  • 50 |
  • https://gist.github.com/JakeWharton/6002797
  • 51 |
  • Should fail gracefully:
  • 52 |
  • https://github.com/snarfed/open-in-app/blob/master/build.xml
  • 53 |
  • http://github.com/snarfed/facebook-atom/issues
  • 54 |
  • http://github.com/rogerhu/mockfacebook/pulls
  • 55 |
  • https://gist.github.com/JakeWharton
  • 56 | 57 | 58 |

    Hulu

    59 | 63 | 64 |

    Goodreads

    65 | 73 | 74 |

    Quip

    75 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /app_manifests/com.hulu.plus_AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/org/snarfed/android/openinapp/Handler.java: -------------------------------------------------------------------------------- 1 | package org.snarfed.android.openinapp; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.regex.Matcher; 7 | import java.util.regex.Pattern; 8 | 9 | import android.app.Activity; 10 | import android.content.ActivityNotFoundException; 11 | import android.content.Intent; 12 | import android.content.pm.ResolveInfo; 13 | import android.os.Bundle; 14 | import android.os.Parcelable; 15 | import android.net.Uri; 16 | import android.util.Log; 17 | import android.widget.Toast; 18 | 19 | import org.yaml.snakeyaml.Yaml; 20 | 21 | public class Handler extends Activity { 22 | static final String TAG = "OpenLinkInApp"; 23 | 24 | @Override 25 | public void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | 28 | Uri uri = getIntent().getData(); 29 | if (uri == null || uri.getPath() == null) { 30 | Log.e(TAG, "No URI in intent!"); 31 | finish(); 32 | return; 33 | } 34 | 35 | // Read config file and find the app for this host 36 | Map config = (Map)new Yaml().load( 37 | getResources().openRawResource(R.raw.apps)); 38 | Map app = null; 39 | for (Map a : (List)config.get("apps")) { 40 | for (String host : (List)a.get("hosts")) { 41 | if (host.equals(uri.getHost())) { 42 | app = a; 43 | break; 44 | } 45 | } 46 | } 47 | 48 | if (app == null) { 49 | Log.e(TAG, "Intent URI " + uri + " has unknown host."); 50 | finish(); 51 | return; 52 | } 53 | 54 | // Build new URI 55 | String base = (String)app.get("new_base"); 56 | if (base == null) { 57 | base = uri.getScheme() + "://" + uri.getHost() + "/"; 58 | } 59 | 60 | List> transforms = (List>) 61 | app.get("uri_transforms"); 62 | String path = uri.getPath(); 63 | if (path.startsWith("/")) { 64 | path = path.substring(1); 65 | } 66 | 67 | if (transforms != null) { 68 | boolean matched = false; 69 | for (Map transform : transforms) { 70 | String from = (String)transform.get("from"); 71 | Matcher matcher = Pattern.compile(from + "/?") 72 | .matcher(path); 73 | if (matcher.matches()) { 74 | path = matcher.replaceFirst(transform.get("to")); 75 | matched = true; 76 | break; 77 | } 78 | } 79 | 80 | if (!matched) { 81 | Log.w(TAG, "URI " + uri + " didn't match any uri_transforms for " + 82 | (String)app.get("name")); 83 | giveUp(uri, app); 84 | return; 85 | } 86 | } 87 | 88 | String newUri = base + path; 89 | String query = uri.getQuery(); 90 | if (query != null) { 91 | newUri += "?" + query; 92 | } 93 | // I don't think intent URIs ever have fragments, but just in case. 94 | String fragment = uri.getFragment(); 95 | if (fragment != null) { 96 | newUri += "#" + fragment; 97 | } 98 | 99 | Intent intent = new Intent(Intent.ACTION_VIEW, uri); 100 | intent.setData(Uri.parse(newUri)); 101 | String pkg = (String)app.get("package"); 102 | if (pkg != null) { 103 | intent.setPackage(pkg); 104 | } 105 | Log.i(TAG, "Redirecting " + getIntent() + " to " + intent); 106 | 107 | // Recommended to prevent occasional Instagram app crashes. Try it if we 108 | // start seeing that. 109 | // https://groups.google.com/d/msg/instagram-api-developers/QmLGb4ImWLU/aXFlNFKfSnUJ 110 | // intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 111 | 112 | try { 113 | startActivity(intent); 114 | finish(); 115 | } catch (ActivityNotFoundException e) { 116 | Log.w(TAG, e); 117 | giveUp(uri, app); 118 | } 119 | } 120 | 121 | public void giveUp(Uri uri, Map app) { 122 | // I couldn't find a way to either 1) filter my own app/activity out of 123 | // the handlers for the resulting mobile.twitter.com intent, or 2) force 124 | // an intent to open only in a browser. CATEGORY_BROWSABLE, 125 | // CATEGORY_APP_BROWSER, set/makeMainSelectorActivity() etc all failed. 126 | // I could limit package to com.android... or com.google... but didn't 127 | // want to ignore a user's different default browser. 128 | // 129 | // So, I create the chooser manually and remove this app and the Twitter 130 | // app (which redirects to mobile.twitter.com). Code based on 131 | // http://hkdevtips.blogspot.com/2013/02/customize-your-actionchooser-intent.html 132 | Toast.makeText(this, "Sorry, " + (String)app.get("name") + 133 | " can't handle this link.", 134 | Toast.LENGTH_LONG).show(); 135 | 136 | Intent intent = new Intent(Intent.ACTION_VIEW, uri); 137 | List apps = new ArrayList(); 138 | String appPkg = (String)app.get("package"); 139 | for (ResolveInfo info : getPackageManager().queryIntentActivities(intent, 0)) { 140 | String pkg = info.activityInfo.packageName; 141 | if (!pkg.equals(getClass().getPackage().getName()) && 142 | !pkg.equals(appPkg)) { 143 | apps.add(new Intent(intent).setPackage(pkg)); 144 | } 145 | } 146 | 147 | // Create the chooser with the first explicit app intent, then add the 148 | // others as extras. 149 | intent = Intent.createChooser(apps.remove(0), null); 150 | intent.putExtra(Intent.EXTRA_INITIAL_INTENTS, apps.toArray(new Parcelable[]{})); 151 | startActivity(intent); 152 | finish(); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /apps.yaml: -------------------------------------------------------------------------------- 1 | # Config file for the external apps we support. Used by generate_manifest.py at 2 | # compile time and Handler.java at runtime. 3 | # 4 | # The schema is: 5 | # 6 | # app: 7 | # -name: NAME # for documentation only 8 | # # These define the data elements in the intent filter. 9 | # hosts: 10 | # ... 11 | # prefixes: (optional) 12 | # ... 13 | # patterns: (optional) 14 | # ... 15 | # # Defaults to prefix / if neither prefixes nor patterns are specified. 16 | # 17 | # # These define the outgoing intent. 18 | # package: PACKAGE (optional) 19 | # new_base: BASE_URL (optional) # e.g. fb:// or http://instagr.am/ 20 | # 21 | # uri_transforms: 22 | # - from: REGEXP # full match against path only 23 | # - to: REGEXP REPLACEMENT # may use $N for matched groups 24 | # 25 | # xxx new_scheme: SCHEME (optional) 26 | # xxx new_host: SCHEME (optional) 27 | # 28 | 29 | schemes: 30 | - http 31 | - https 32 | 33 | 34 | apps: 35 | - name: Facebook 36 | # Facebook app intent details in http://stackoverflow.com/questions/6179776 37 | hosts: 38 | - facebook.com 39 | - www.facebook.com 40 | package: com.facebook.katana 41 | new_base: fb:// 42 | uri_transforms: 43 | # These use the fb:// scheme: 44 | # http://stackoverflow.com/a/6638342/186123 45 | # http://wiki.akosma.com/IPhone_URL_Schemes#Facebook 46 | 47 | # User id: /212038 48 | - from: ([0-9]+) 49 | to: profile/$1 50 | # Page: /pages/mockfb/225279024204684 51 | - from: pages/([^/.?]+)/([^/.?]+) 52 | to: page/$2 53 | # Post: /504988744/posts/10151785603608745 54 | - from: ([^/.?]+)/posts/([^/.?]+) 55 | to: post/$1_$2?owner=$1 56 | # Group: /groups/257050967664385/ 57 | - from: groups/([^/.?]+) 58 | to: group/$1 59 | 60 | # Username: /snarfed.org 61 | # Looks like the Facebook app doesn't handle username-based profile URIs yet. 62 | # http://stackoverflow.com/questions/9281267/how-do-i-link-to-a-facebook-user-in-the-ios-app-by-using-the-username 63 | # The old way of passing it in "extra_user_display_name" no longer works: 64 | # http://forum.frandroid.com/topic/22299-facebooktwitter-intent/ 65 | # We could manually look up their user id with the Graph API, but 66 | # that's a bit heavyweight. 67 | # - from: ([^/]+) 68 | # to: profile/$1 69 | 70 | # Photo: 71 | # /photo.php?fbid=522958101087051&set=at.113298848719647.8466.100001185986830.212743&type=1&theater 72 | # Doesn't work yet. 73 | # - from: photo\.php\?(.*)fbid=([0-9]+) 74 | # to: photos?photo=$2 75 | 76 | 77 | - name: GitHub 78 | # GitHub's UriLauncherActivity registers to handle all URIs, but doesn't 79 | # actually handle some of them: 80 | # Files: /snarfed/open-in-app/blob/master/build.xml 81 | # Issue browser: /snarfed/facebook-atom/issues 82 | # Pull request browser: /rogerhu/mockfacebook/pulls 83 | hosts: 84 | - github.com 85 | - www.github.com 86 | package: com.github.mobile 87 | new_base: https://github.com/ 88 | uri_transforms: 89 | # User: /snarfed 90 | - from: '[^/]+' 91 | to: $0 92 | # Repo: /snarfed/open-in-app 93 | - from: '[^/]+/[^/]+' 94 | to: $0 95 | # Commit: /snarfed/open-in-app/commit/a8865ac8d7bd13667287943a8b9e81b8eb970629 96 | - from: '[^/]+/[^/]+/commit/[^/]+' 97 | to: $0 98 | # Issue: /snarfed/facebook-atom/issues/1 99 | - from: '[^/]+/[^/]+/issues/[0-9]+' 100 | to: $0 101 | # Pull request: /rogerhu/mockfacebook/pull/22 102 | - from: '[^/]+/[^/]+/pull/[0-9]+' 103 | to: $0 104 | 105 | 106 | - name: GitHub Gist 107 | # Only supports gists, not user profiles. 108 | hosts: 109 | - gist.github.com 110 | package: com.github.mobile 111 | uri_transforms: 112 | # /6002797 113 | # /JakeWharton/6002797 114 | # /abcdef1234567890abcd 115 | - from: ([^/]+/)?([0-9]+|[a-f0-9]{20}) 116 | to: $2 117 | 118 | 119 | # Google+ is incredibly thorough about handling all the URLs it can, and it 120 | # registers them all so that it handles link taps in browsers. I have nothing 121 | # more to add. :P 122 | # - name: Google+ 123 | # package: com.google.android.apps.plus 124 | 125 | 126 | # Goodreads doesn't register for browser link taps, but it does handle all of 127 | # its URLs, so just pass them through unchanged. 128 | - name: Goodreads 129 | hosts: 130 | - goodreads.com 131 | - www.goodreads.com 132 | - m.goodreads.com 133 | package: com.goodreads 134 | 135 | 136 | # Hulu supports URLs with the /show-mobile/ and /watch-mobile/ prefixes, but I 137 | # haven't yet figured out their formats. Naive URLs like 138 | # http://hulu.com/show-mobile/30-rock and http://hulu.com/watch-mobile/449173 139 | # don't work. 140 | # - name: Hulu 141 | # package: com.hulu.plus 142 | 143 | 144 | - name: Instagram 145 | # Currently the Instagram app only handles intents for opening pictures and 146 | # resetting your password, not opening user profiles. 147 | # 148 | # https://groups.google.com/forum/#!msg/instagram-api-developers/7XUKm9HSAdg/9SrdVmB4trQJ 149 | # http://stackoverflow.com/questions/15497261/open-instagram-user-profile 150 | # 151 | # There is an instagram:// scheme, but evidently it's iOS only. 152 | # http://instagram.com/developer/iphone-hooks/ 153 | hosts: 154 | - instagram.com 155 | - www.instagram.com 156 | prefixes: 157 | - /p/ 158 | # This is the package Instagram sets in its manifest, but I get 159 | # ActivityNotFoundException when I try to use it. 160 | # package: com.instagram.android 161 | new_base: http://instagr.am/ 162 | 163 | 164 | - name: Twitter mobile 165 | # Just convert the mobile.twitter.com domain to just twitter.com. The Twitter 166 | # app doesn't handle mobile.twitter.com URLs for some reason. 167 | # http://omgwtfgames.com/2012/01/android-intents-captured-by-various-twitter-clients/ 168 | # http://wiki.akosma.com/IPhone_URL_Schemes#Twitter 169 | hosts: 170 | - mobile.twitter.com 171 | package: com.twitter.android 172 | new_base: https://twitter.com/ 173 | 174 | 175 | - name: Twitter 176 | hosts: 177 | - twitter.com 178 | - www.twitter.com 179 | # The official Twitter app handles /intent/... but then just redirects to the 180 | # browser. It doesn't have native intents for handling retweeting, favorites, 181 | # etc. though, so there's nothing I could really do here anyway. 182 | # prefixes: 183 | # - /intent/ 184 | package: com.twitter.android 185 | 186 | 187 | # - name: WordPress 188 | # package: org.wordpress.android 189 | -------------------------------------------------------------------------------- /app_manifests/org.wordpress.android_AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /app_manifests/com.opentable_AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /app_manifests/com.goodreads_AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /app_manifests/com.instagram.android_AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /app_manifests/intent_filters: -------------------------------------------------------------------------------- 1 | facebook 2 | === 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | github 26 | === 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | goodreads 50 | === 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | calendar 64 | === 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | hulu 77 | === 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | twitter 94 | === 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | instagram 108 | === 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | google+ 121 | === 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /app_manifests/com.github.mobile_AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /app_manifests/com.android.calendar_AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /app_manifests/com.google.android.calendar_AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 14 | 15 | 18 | 19 | 22 | 23 | 26 | 27 | 30 | 31 | 34 | 35 | 38 | 39 | 42 | 43 | 46 | 47 | 50 | 51 | 54 | 55 | 58 | 59 | 62 | 63 | 66 | 67 | 70 | 71 | 74 | 75 | 78 | 79 | 82 | 83 | 86 | 87 | 90 | 91 | 94 | 95 | 98 | 99 | 102 | 103 | 107 | 108 | 111 | 112 | 122 | 126 | 127 | 134 | 136 | 139 | 140 | 143 | 144 | 147 | 148 | 151 | 152 | 153 | 155 | 158 | 159 | 162 | 163 | 166 | 167 | 170 | 171 | 174 | 175 | 176 | 177 | 182 | 183 | 188 | 190 | 193 | 194 | 197 | 198 | 199 | 201 | 204 | 205 | 208 | 209 | 212 | 213 | 214 | 218 | 219 | 220 | 224 | 225 | 230 | 232 | 235 | 236 | 239 | 240 | 243 | 244 | 247 | 248 | 249 | 251 | 254 | 255 | 258 | 259 | 262 | 263 | 266 | 267 | 268 | 269 | 275 | 278 | 281 | 282 | 285 | 286 | 289 | 290 | 295 | 296 | 301 | 302 | 307 | 308 | 313 | 314 | 315 | 316 | 321 | 322 | 327 | 328 | 333 | 334 | 338 | 339 | 347 | 349 | 352 | 353 | 354 | 358 | 359 | 360 | 365 | 366 | 372 | 373 | 380 | 381 | 387 | 388 | 394 | 395 | 398 | 400 | 403 | 404 | 407 | 408 | 411 | 412 | 413 | 415 | 418 | 419 | 420 | 422 | 425 | 426 | 427 | 429 | 432 | 433 | 436 | 437 | 440 | 441 | 442 | 443 | 448 | 450 | 453 | 454 | 457 | 458 | 459 | 460 | 464 | 466 | 469 | 470 | 471 | 472 | 475 | 476 | 479 | 480 | 483 | 484 | 487 | 488 | 492 | 494 | 497 | 498 | 501 | 502 | 503 | 507 | 508 | 509 | 512 | 514 | 517 | 518 | 521 | 522 | 525 | 526 | 529 | 530 | 531 | 533 | 536 | 537 | 540 | 541 | 544 | 545 | 546 | 548 | 551 | 552 | 555 | 556 | 559 | 560 | 563 | 564 | 565 | 566 | 571 | 572 | 575 | 576 | 580 | 582 | 585 | 586 | 587 | 591 | 592 | 593 | 596 | 598 | 601 | 602 | 603 | 604 | 607 | 608 | 611 | 612 | 613 | 614 | -------------------------------------------------------------------------------- /app_manifests/com.twitter.android_AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | -------------------------------------------------------------------------------- /app_manifests/com.google.android.apps.plus_AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | --------------------------------------------------------------------------------