summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
authorDianne Hackborn <hackbod@google.com>2014-11-04 10:31:54 -0800
committerDianne Hackborn <hackbod@google.com>2014-11-11 00:42:18 +0000
commit85d558cd486d195aabfc4b43cff8f338126f60a5 (patch)
tree0a228ca6ffb9079635434a740abeeece370f055d /core/java/android
parent993e3d2d40cbd95d78cd3d2a76c77af2f4ab0e88 (diff)
downloadframeworks_base-85d558cd486d195aabfc4b43cff8f338126f60a5.zip
frameworks_base-85d558cd486d195aabfc4b43cff8f338126f60a5.tar.gz
frameworks_base-85d558cd486d195aabfc4b43cff8f338126f60a5.tar.bz2
Add Activity API to get referrer information.
This expands the use of EXTRA_REFERRER to be relevant anywhere, allowing apps to supply referrer information if they want. However, if they don't explicitly supply it, then the platform now keeps track of package names that go with Intents when delivering them to apps, which it can be returned as the default value. The new method Activity.getReferrer() is used to retrieve this referrer information. It knows about EXTRA_REFERRER, it can return the default package name tracked internally, and it also can return a new EXTRA_REFERRER_NAME if that exists. The latter is needed because we can't use EXTRA_REFERRER in some cases since it is a Uri, and things like #Intent; URI extras can only generate primitive type extras. We really need to support this syntax for referrers, so we need to have this additional extra field as an option. When a referrer is to a native app, we are adopting the android-app scheme. Since we are doing this, Intent's URI creation and parsing now supports this scheme, and we improve its syntax to be able to build intents with custom actions and stuff, instead of being all hung up on custom schemes. While doing this, fixed a problem when parsing both intent: and new android-app: schemes with a selector portion, where we were not respecting any scheme that was specified. Change-Id: I06e55221e21a8156c1d6ac755a254fea386917a2
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/app/Activity.java37
-rw-r--r--core/java/android/app/ActivityThread.java25
-rw-r--r--core/java/android/app/ApplicationThreadNative.java22
-rw-r--r--core/java/android/app/IApplicationThread.java9
-rw-r--r--core/java/android/app/Instrumentation.java15
-rw-r--r--core/java/android/app/LocalActivityManager.java5
-rw-r--r--core/java/android/content/Intent.java266
-rw-r--r--core/java/android/content/pm/LabeledIntent.aidl19
8 files changed, 340 insertions, 58 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4b705dd..148527f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -693,6 +693,7 @@ public class Activity extends ContextThemeWrapper
/*package*/ String mEmbeddedID;
private Application mApplication;
/*package*/ Intent mIntent;
+ /*package*/ String mReferrer;
private ComponentName mComponent;
/*package*/ ActivityInfo mActivityInfo;
/*package*/ ActivityThread mMainThread;
@@ -4448,6 +4449,39 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Return information about who launched this activity. If the launching Intent
+ * contains an {@link android.content.Intent#EXTRA_REFERRER Intent.EXTRA_REFERRER},
+ * that will be returned as-is; otherwise, if known, an
+ * {@link Intent#URI_ANDROID_APP_SCHEME android-app:} referrer URI containing the
+ * package name that started the Intent will be returned. This may return null if no
+ * referrer can be identified -- it is neither explicitly specified, nor is it known which
+ * application package was involved.
+ *
+ * <p>If called while inside the handling of {@link #onNewIntent}, this function will
+ * return the referrer that submitted that new intent to the activity. Otherwise, it
+ * always returns the referrer of the original Intent.</p>
+ *
+ * <p>Note that this is <em>not</em> a security feature -- you can not trust the
+ * referrer information, applications can spoof it.</p>
+ */
+ @Nullable
+ public Uri getReferrer() {
+ Intent intent = getIntent();
+ Uri referrer = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
+ if (referrer != null) {
+ return referrer;
+ }
+ String referrerName = intent.getStringExtra(Intent.EXTRA_REFERRER_NAME);
+ if (referrerName != null) {
+ return Uri.parse(referrerName);
+ }
+ if (mReferrer != null) {
+ return new Uri.Builder().scheme("android-app").authority(mReferrer).build();
+ }
+ return null;
+ }
+
+ /**
* Return the name of the package that invoked this activity. This is who
* the data in {@link #setResult setResult()} will be sent to. You can
* use this information to validate that the recipient is allowed to
@@ -5868,7 +5902,7 @@ public class Activity extends ContextThemeWrapper
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
- Configuration config, IVoiceInteractor voiceInteractor) {
+ Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);
mFragments.attachActivity(this, mContainer, null);
@@ -5891,6 +5925,7 @@ public class Activity extends ContextThemeWrapper
mIdent = ident;
mApplication = application;
mIntent = intent;
+ mReferrer = referrer;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index cf6c049..5f21d75 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -94,6 +94,7 @@ import android.renderscript.RenderScript;
import android.security.AndroidKeyStoreProvider;
import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.RuntimeInit;
import com.android.internal.os.SamplingProfilerIntegration;
@@ -268,6 +269,7 @@ public final class ActivityThread {
IBinder token;
int ident;
Intent intent;
+ String referrer;
IVoiceInteractor voiceInteractor;
Bundle state;
PersistableBundle persistentState;
@@ -290,7 +292,7 @@ public final class ActivityThread {
LoadedApk packageInfo;
List<ResultInfo> pendingResults;
- List<Intent> pendingIntents;
+ List<ReferrerIntent> pendingIntents;
boolean startsNotResumed;
boolean isForward;
@@ -348,7 +350,7 @@ public final class ActivityThread {
}
static final class NewIntentData {
- List<Intent> intents;
+ List<ReferrerIntent> intents;
IBinder token;
public String toString() {
return "NewIntentData{intents=" + intents + " token=" + token + "}";
@@ -605,9 +607,9 @@ public final class ActivityThread {
// activity itself back to the activity manager. (matters more with ipc)
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
- IVoiceInteractor voiceInteractor, int procState, Bundle state,
+ String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
- List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
+ List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
@@ -617,6 +619,7 @@ public final class ActivityThread {
r.token = token;
r.ident = ident;
r.intent = intent;
+ r.referrer = referrer;
r.voiceInteractor = voiceInteractor;
r.activityInfo = info;
r.compatInfo = compatInfo;
@@ -637,13 +640,13 @@ public final class ActivityThread {
}
public final void scheduleRelaunchActivity(IBinder token,
- List<ResultInfo> pendingResults, List<Intent> pendingNewIntents,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
int configChanges, boolean notResumed, Configuration config) {
requestRelaunchActivity(token, pendingResults, pendingNewIntents,
configChanges, notResumed, config, true);
}
- public final void scheduleNewIntent(List<Intent> intents, IBinder token) {
+ public final void scheduleNewIntent(List<ReferrerIntent> intents, IBinder token) {
NewIntentData data = new NewIntentData();
data.intents = intents;
data.token = token;
@@ -2234,7 +2237,7 @@ public final class ActivityThread {
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
- r.voiceInteractor);
+ r.referrer, r.voiceInteractor);
if (customIntent != null) {
activity.mIntent = customIntent;
@@ -2421,8 +2424,7 @@ public final class ActivityThread {
}
}
- private void deliverNewIntents(ActivityClientRecord r,
- List<Intent> intents) {
+ private void deliverNewIntents(ActivityClientRecord r, List<ReferrerIntent> intents) {
final int N = intents.size();
for (int i=0; i<N; i++) {
Intent intent = intents.get(i);
@@ -2433,8 +2435,7 @@ public final class ActivityThread {
}
}
- public final void performNewIntents(IBinder token,
- List<Intent> intents) {
+ public final void performNewIntents(IBinder token, List<ReferrerIntent> intents) {
ActivityClientRecord r = mActivities.get(token);
if (r != null) {
final boolean resumed = !r.paused;
@@ -3752,7 +3753,7 @@ public final class ActivityThread {
}
public final void requestRelaunchActivity(IBinder token,
- List<ResultInfo> pendingResults, List<Intent> pendingNewIntents,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
int configChanges, boolean notResumed, Configuration config,
boolean fromServer) {
ActivityClientRecord target = null;
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index 0123e16..d1b77b9 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -36,6 +36,7 @@ import android.os.IBinder;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -140,19 +141,21 @@ public abstract class ApplicationThreadNative extends Binder
ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data);
Configuration curConfig = Configuration.CREATOR.createFromParcel(data);
CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
+ String referrer = data.readString();
IVoiceInteractor voiceInteractor = IVoiceInteractor.Stub.asInterface(
data.readStrongBinder());
int procState = data.readInt();
Bundle state = data.readBundle();
PersistableBundle persistentState = data.readPersistableBundle();
List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR);
- List<Intent> pi = data.createTypedArrayList(Intent.CREATOR);
+ List<ReferrerIntent> pi = data.createTypedArrayList(ReferrerIntent.CREATOR);
boolean notResumed = data.readInt() != 0;
boolean isForward = data.readInt() != 0;
ProfilerInfo profilerInfo = data.readInt() != 0
? ProfilerInfo.CREATOR.createFromParcel(data) : null;
- scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, voiceInteractor,
- procState, state, persistentState, ri, pi, notResumed, isForward, profilerInfo);
+ scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, referrer,
+ voiceInteractor, procState, state, persistentState, ri, pi,
+ notResumed, isForward, profilerInfo);
return true;
}
@@ -161,7 +164,7 @@ public abstract class ApplicationThreadNative extends Binder
data.enforceInterface(IApplicationThread.descriptor);
IBinder b = data.readStrongBinder();
List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR);
- List<Intent> pi = data.createTypedArrayList(Intent.CREATOR);
+ List<ReferrerIntent> pi = data.createTypedArrayList(ReferrerIntent.CREATOR);
int configChanges = data.readInt();
boolean notResumed = data.readInt() != 0;
Configuration config = null;
@@ -175,7 +178,7 @@ public abstract class ApplicationThreadNative extends Binder
case SCHEDULE_NEW_INTENT_TRANSACTION:
{
data.enforceInterface(IApplicationThread.descriptor);
- List<Intent> pi = data.createTypedArrayList(Intent.CREATOR);
+ List<ReferrerIntent> pi = data.createTypedArrayList(ReferrerIntent.CREATOR);
IBinder b = data.readStrongBinder();
scheduleNewIntent(pi, b);
return true;
@@ -764,9 +767,9 @@ class ApplicationThreadProxy implements IApplicationThread {
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
- IVoiceInteractor voiceInteractor, int procState, Bundle state,
+ String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
- List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
+ List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
ProfilerInfo profilerInfo) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
@@ -776,6 +779,7 @@ class ApplicationThreadProxy implements IApplicationThread {
info.writeToParcel(data, 0);
curConfig.writeToParcel(data, 0);
compatInfo.writeToParcel(data, 0);
+ data.writeString(referrer);
data.writeStrongBinder(voiceInteractor != null ? voiceInteractor.asBinder() : null);
data.writeInt(procState);
data.writeBundle(state);
@@ -796,7 +800,7 @@ class ApplicationThreadProxy implements IApplicationThread {
}
public final void scheduleRelaunchActivity(IBinder token,
- List<ResultInfo> pendingResults, List<Intent> pendingNewIntents,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
int configChanges, boolean notResumed, Configuration config)
throws RemoteException {
Parcel data = Parcel.obtain();
@@ -817,7 +821,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
- public void scheduleNewIntent(List<Intent> intents, IBinder token)
+ public void scheduleNewIntent(List<ReferrerIntent> intents, IBinder token)
throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index f53075c..42acbc6 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -35,6 +35,7 @@ import android.os.IBinder;
import android.os.IInterface;
import android.service.voice.IVoiceInteractionSession;
import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
import java.io.FileDescriptor;
import java.util.List;
@@ -59,14 +60,14 @@ public interface IApplicationThread extends IInterface {
void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException;
void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
- IVoiceInteractor voiceInteractor, int procState, Bundle state,
+ String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
- List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
+ List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
ProfilerInfo profilerInfo) throws RemoteException;
void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults,
- List<Intent> pendingNewIntents, int configChanges,
+ List<ReferrerIntent> pendingNewIntents, int configChanges,
boolean notResumed, Configuration config) throws RemoteException;
- void scheduleNewIntent(List<Intent> intent, IBinder token) throws RemoteException;
+ void scheduleNewIntent(List<ReferrerIntent> intent, IBinder token) throws RemoteException;
void scheduleDestroyActivity(IBinder token, boolean finished,
int configChanges) throws RemoteException;
void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo,
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 60a013e..d96153a 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -45,6 +45,7 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.Window;
+import com.android.internal.content.ReferrerIntent;
import java.io.File;
import java.util.ArrayList;
@@ -1042,7 +1043,7 @@ public class Instrumentation {
activity.attach(context, aThread, this, token, 0, application, intent,
info, title, parent, id,
(Activity.NonConfigurationInstances)lastNonConfigurationInstance,
- new Configuration(), null);
+ new Configuration(), null, null);
return activity;
}
@@ -1207,7 +1208,17 @@ public class Instrumentation {
* @param intent The new intent being received.
*/
public void callActivityOnNewIntent(Activity activity, Intent intent) {
- activity.onNewIntent(intent);
+ final String oldReferrer = activity.mReferrer;
+ try {
+ try {
+ activity.mReferrer = ((ReferrerIntent)intent).mReferrer;
+ } catch (ClassCastException e) {
+ activity.mReferrer = null;
+ }
+ activity.onNewIntent(intent);
+ } finally {
+ activity.mReferrer = oldReferrer;
+ }
}
/**
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index b654a6a..873e337 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -22,6 +22,7 @@ import android.os.Binder;
import android.os.Bundle;
import android.util.Log;
import android.view.Window;
+import com.android.internal.content.ReferrerIntent;
import java.util.ArrayList;
import java.util.HashMap;
@@ -310,8 +311,8 @@ public class LocalActivityManager {
if (aInfo.launchMode != ActivityInfo.LAUNCH_MULTIPLE ||
(intent.getFlags()&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0) {
// The activity wants onNewIntent() called.
- ArrayList<Intent> intents = new ArrayList<Intent>(1);
- intents.add(intent);
+ ArrayList<ReferrerIntent> intents = new ArrayList<>(1);
+ intents.add(new ReferrerIntent(intent, mParent.getPackageName()));
if (localLOGV) Log.v(TAG, r.id + ": new intent");
mActivityThread.performNewIntents(r, intents);
r.intent = intent;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e06f034..57f6028 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1401,14 +1401,36 @@ public class Intent implements Parcelable, Cloneable {
= "android.intent.extra.ORIGINATING_URI";
/**
- * Used as a URI extra field with {@link #ACTION_INSTALL_PACKAGE} and
- * {@link #ACTION_VIEW} to indicate the HTTP referrer URI associated with the Intent
- * data field or {@link #EXTRA_ORIGINATING_URI}.
+ * This extra can be used with any Intent used to launch an activity, supplying information
+ * about who is launching that activity. This field contains a {@link android.net.Uri}
+ * object, typically an http: or https: URI of the web site that the referral came from;
+ * it can also use the {@link #URI_ANDROID_APP_SCHEME android-app:} scheme to identify
+ * a native application that it came from.
+ *
+ * <p>To retrieve this value in a client, use {@link android.app.Activity#getReferrer}
+ * instead of directly retrieving the extra. It is also valid for applications to
+ * instead supply {@link #EXTRA_REFERRER_NAME} for cases where they can only create
+ * a string, not a Uri; the field here, if supplied, will always take precedence,
+ * however.</p>
+ *
+ * @see #EXTRA_REFERRER_NAME
*/
public static final String EXTRA_REFERRER
= "android.intent.extra.REFERRER";
/**
+ * Alternate version of {@link #EXTRA_REFERRER} that supplies the URI as a String rather
+ * than a {@link android.net.Uri} object. Only for use in cases where Uri objects can
+ * not be created, in particular when Intent extras are supplied through the
+ * {@link #URI_INTENT_SCHEME intent:} or {@link #URI_ANDROID_APP_SCHEME android-app:}
+ * schemes.
+ *
+ * @see #EXTRA_REFERRER
+ */
+ public static final String EXTRA_REFERRER_NAME
+ = "android.intent.extra.REFERRER_NAME";
+
+ /**
* Used as an int extra field with {@link #ACTION_INSTALL_PACKAGE} and
* {@link} #ACTION_VIEW} to indicate the uid of the package that initiated the install
* @hide
@@ -3919,6 +3941,75 @@ public class Intent implements Parcelable, Cloneable {
*/
public static final int URI_INTENT_SCHEME = 1<<0;
+ /**
+ * Flag for use with {@link #toUri} and {@link #parseUri}: the URI string
+ * always has the "android-app:" scheme. This is a variation of
+ * {@link #URI_INTENT_SCHEME} whose format is simpler for the case of an
+ * http/https URI being delivered to a specific package name. The format
+ * is:
+ *
+ * <pre class="prettyprint">
+ * android-app://{package_id}/{scheme}/{host}/{path}{#Intent;...}</pre>
+ *
+ * <p>In this scheme, only the <code>pacakge_id</code> is required, and all
+ * other components can be included as desired. Note that this can not be
+ * used with intents that have a {@link #setSelector}, since the base intent
+ * will always have an explicit package name.</p>
+ *
+ * <p>Some examples of how this scheme maps to Intent objects:</p>
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ * <colgroup align="left" />
+ * <colgroup align="left" />
+ * <thead>
+ * <tr><th>URI</th> <th>Intent</th></tr>
+ * </thead>
+ *
+ * <tbody>
+ * <tr><td><code>android-app://com.example.app</code></td>
+ * <td><table style="margin:0;border:0;cellpadding:0;cellspacing:0">
+ * <tr><td>Action: </td><td>{@link #ACTION_MAIN}</td></tr>
+ * <tr><td>Package: </td><td><code>com.example.app</code></td></tr>
+ * </table></td>
+ * </tr>
+ * <tr><td><code>android-app://com.example.app/http/example.com</code></td>
+ * <td><table style="margin:0;border:0;cellpadding:0;cellspacing:0">
+ * <tr><td>Action: </td><td>{@link #ACTION_VIEW}</td></tr>
+ * <tr><td>Data: </td><td><code>http://example.com/</code></td></tr>
+ * <tr><td>Package: </td><td><code>com.example.app</code></td></tr>
+ * </table></td>
+ * </tr>
+ * <tr><td><code>android-app://com.example.app/http/example.com/foo?1234</code></td>
+ * <td><table style="margin:0;border:0;cellpadding:0;cellspacing:0">
+ * <tr><td>Action: </td><td>{@link #ACTION_VIEW}</td></tr>
+ * <tr><td>Data: </td><td><code>http://example.com/foo?1234</code></td></tr>
+ * <tr><td>Package: </td><td><code>com.example.app</code></td></tr>
+ * </table></td>
+ * </tr>
+ * <tr><td><code>android-app://com.example.app/<br />#Intent;action=com.example.MY_ACTION;end</code></td>
+ * <td><table style="margin:0;border:0;cellpadding:0;cellspacing:0">
+ * <tr><td>Action: </td><td><code>com.example.MY_ACTION</code></td></tr>
+ * <tr><td>Package: </td><td><code>com.example.app</code></td></tr>
+ * </table></td>
+ * </tr>
+ * <tr><td><code>android-app://com.example.app/http/example.com/foo?1234<br />#Intent;action=com.example.MY_ACTION;end</code></td>
+ * <td><table style="margin:0;border:0;cellpadding:0;cellspacing:0">
+ * <tr><td>Action: </td><td><code>com.example.MY_ACTION</code></td></tr>
+ * <tr><td>Data: </td><td><code>http://example.com/foo?1234</code></td></tr>
+ * <tr><td>Package: </td><td><code>com.example.app</code></td></tr>
+ * </table></td>
+ * </tr>
+ * <tr><td><code>android-app://com.example.app/<br />#Intent;action=com.example.MY_ACTION;<br />i.some_int=100;S.some_str=hello;end</code></td>
+ * <td><table border="" style="margin:0" >
+ * <tr><td>Action: </td><td><code>com.example.MY_ACTION</code></td></tr>
+ * <tr><td>Package: </td><td><code>com.example.app</code></td></tr>
+ * <tr><td>Extras: </td><td><code>some_int=(int)100<br />some_str=(String)hello</code></td></tr>
+ * </table></td>
+ * </tr>
+ * </tbody>
+ * </table>
+ */
+ public static final int URI_ANDROID_APP_SCHEME = 1<<1;
+
// ---------------------------------------------------------------------
private String mAction;
@@ -4179,8 +4270,8 @@ public class Intent implements Parcelable, Cloneable {
* the scheme and full path.
*
* @param uri The URI to turn into an Intent.
- * @param flags Additional processing flags. Either 0 or
- * {@link #URI_INTENT_SCHEME}.
+ * @param flags Additional processing flags. Either 0,
+ * {@link #URI_INTENT_SCHEME}, or {@link #URI_ANDROID_APP_SCHEME}.
*
* @return Intent The newly created Intent object.
*
@@ -4193,9 +4284,11 @@ public class Intent implements Parcelable, Cloneable {
public static Intent parseUri(String uri, int flags) throws URISyntaxException {
int i = 0;
try {
- // Validate intent scheme for if requested.
- if ((flags&URI_INTENT_SCHEME) != 0) {
- if (!uri.startsWith("intent:")) {
+ final boolean androidApp = uri.startsWith("android-app:");
+
+ // Validate intent scheme if requested.
+ if ((flags&(URI_INTENT_SCHEME|URI_ANDROID_APP_SCHEME)) != 0) {
+ if (!uri.startsWith("intent:") && !androidApp) {
Intent intent = new Intent(ACTION_VIEW);
try {
intent.setData(Uri.parse(uri));
@@ -4206,24 +4299,40 @@ public class Intent implements Parcelable, Cloneable {
}
}
- // simple case
i = uri.lastIndexOf("#");
- if (i == -1) return new Intent(ACTION_VIEW, Uri.parse(uri));
+ // simple case
+ if (i == -1) {
+ if (!androidApp) {
+ return new Intent(ACTION_VIEW, Uri.parse(uri));
+ }
// old format Intent URI
- if (!uri.startsWith("#Intent;", i)) return getIntentOld(uri);
+ } else if (!uri.startsWith("#Intent;", i)) {
+ if (!androidApp) {
+ return getIntentOld(uri);
+ } else {
+ i = -1;
+ }
+ }
// new format
Intent intent = new Intent(ACTION_VIEW);
Intent baseIntent = intent;
+ boolean explicitAction = false;
+ boolean inSelector = false;
// fetch data part, if present
- String data = i >= 0 ? uri.substring(0, i) : null;
String scheme = null;
- i += "#Intent;".length();
+ String data;
+ if (i >= 0) {
+ data = uri.substring(0, i);
+ i += 8; // length of "#Intent;"
+ } else {
+ data = uri;
+ }
// loop over contents of Intent, all name=value;
- while (!uri.startsWith("end", i)) {
+ while (i >= 0 && !uri.startsWith("end", i)) {
int eq = uri.indexOf('=', i);
if (eq < 0) eq = i-1;
int semi = uri.indexOf(';', i);
@@ -4232,6 +4341,9 @@ public class Intent implements Parcelable, Cloneable {
// action
if (uri.startsWith("action=", i)) {
intent.setAction(value);
+ if (!inSelector) {
+ explicitAction = true;
+ }
}
// categories
@@ -4261,7 +4373,11 @@ public class Intent implements Parcelable, Cloneable {
// scheme
else if (uri.startsWith("scheme=", i)) {
- scheme = value;
+ if (inSelector) {
+ intent.mData = Uri.parse(value);
+ } else {
+ scheme = value;
+ }
}
// source bounds
@@ -4272,6 +4388,7 @@ public class Intent implements Parcelable, Cloneable {
// selector
else if (semi == (i+3) && uri.startsWith("SEL", i)) {
intent = new Intent();
+ inSelector = true;
}
// extra
@@ -4297,9 +4414,11 @@ public class Intent implements Parcelable, Cloneable {
i = semi + 1;
}
- if (intent != baseIntent) {
+ if (inSelector) {
// The Intent had a selector; fix it up.
- baseIntent.setSelector(intent);
+ if (baseIntent.mPackage == null) {
+ baseIntent.setSelector(intent);
+ }
intent = baseIntent;
}
@@ -4309,6 +4428,47 @@ public class Intent implements Parcelable, Cloneable {
if (scheme != null) {
data = scheme + ':' + data;
}
+ } else if (data.startsWith("android-app:")) {
+ if (data.charAt(12) == '/' && data.charAt(13) == '/') {
+ // Correctly formed android-app, first part is package name.
+ int end = data.indexOf('/', 14);
+ if (end < 0) {
+ // All we have is a package name.
+ intent.mPackage = data.substring(14);
+ if (!explicitAction) {
+ intent.setAction(ACTION_MAIN);
+ }
+ data = "";
+ } else {
+ // Target the Intent at the given package name always.
+ String authority = null;
+ intent.mPackage = data.substring(14, end);
+ int newEnd;
+ if (end < data.length() && (newEnd=data.indexOf('/', end+1)) >= 0) {
+ // Found a scheme, remember it.
+ scheme = data.substring(end+1, newEnd);
+ end = newEnd;
+ if (end < data.length() && (newEnd=data.indexOf('/', end+1)) >= 0) {
+ // Found a authority, remember it.
+ authority = data.substring(end+1, newEnd);
+ end = newEnd;
+ }
+ }
+ if (scheme == null) {
+ // If there was no scheme, then this just targets the package.
+ if (!explicitAction) {
+ intent.setAction(ACTION_MAIN);
+ }
+ data = "";
+ } else if (authority == null) {
+ data = scheme + ":";
+ } else {
+ data = scheme + "://" + authority + data.substring(end);
+ }
+ }
+ } else {
+ data = "";
+ }
}
if (data.length() > 0) {
@@ -7084,14 +7244,53 @@ public class Intent implements Parcelable, Cloneable {
* <p>You can convert the returned string back to an Intent with
* {@link #getIntent}.
*
- * @param flags Additional operating flags. Either 0 or
- * {@link #URI_INTENT_SCHEME}.
+ * @param flags Additional operating flags. Either 0,
+ * {@link #URI_INTENT_SCHEME}, or {@link #URI_ANDROID_APP_SCHEME}.
*
* @return Returns a URI encoding URI string describing the entire contents
* of the Intent.
*/
public String toUri(int flags) {
StringBuilder uri = new StringBuilder(128);
+ if ((flags&URI_ANDROID_APP_SCHEME) != 0) {
+ if (mPackage == null) {
+ throw new IllegalArgumentException(
+ "Intent must include an explicit package name to build an android-app: "
+ + this);
+ }
+ uri.append("android-app://");
+ uri.append(mPackage);
+ String scheme = null;
+ if (mData != null) {
+ scheme = mData.getScheme();
+ if (scheme != null) {
+ uri.append('/');
+ uri.append(scheme);
+ String authority = mData.getEncodedAuthority();
+ if (authority != null) {
+ uri.append('/');
+ uri.append(authority);
+ String path = mData.getEncodedPath();
+ if (path != null) {
+ uri.append(path);
+ }
+ String queryParams = mData.getEncodedQuery();
+ if (queryParams != null) {
+ uri.append('?');
+ uri.append(queryParams);
+ }
+ String fragment = mData.getEncodedFragment();
+ if (fragment != null) {
+ uri.append('#');
+ uri.append(fragment);
+ }
+ }
+ }
+ }
+ toUriFragment(uri, null, scheme == null ? Intent.ACTION_MAIN : Intent.ACTION_VIEW,
+ mPackage, flags);
+ return uri.toString();
+ }
String scheme = null;
if (mData != null) {
String data = mData.toString();
@@ -7121,27 +7320,38 @@ public class Intent implements Parcelable, Cloneable {
uri.append("intent:");
}
- uri.append("#Intent;");
+ toUriFragment(uri, scheme, Intent.ACTION_VIEW, null, flags);
+
+ return uri.toString();
+ }
+
+ private void toUriFragment(StringBuilder uri, String scheme, String defAction,
+ String defPackage, int flags) {
+ StringBuilder frag = new StringBuilder(128);
- toUriInner(uri, scheme, flags);
+ toUriInner(frag, scheme, defAction, defPackage, flags);
if (mSelector != null) {
uri.append("SEL;");
// Note that for now we are not going to try to handle the
// data part; not clear how to represent this as a URI, and
// not much utility in it.
- mSelector.toUriInner(uri, null, flags);
+ mSelector.toUriInner(frag, mSelector.mData != null ? mSelector.mData.getScheme() : null,
+ null, null, flags);
}
- uri.append("end");
-
- return uri.toString();
+ if (frag.length() > 0) {
+ uri.append("#Intent;");
+ uri.append(frag);
+ uri.append("end");
+ }
}
- private void toUriInner(StringBuilder uri, String scheme, int flags) {
+ private void toUriInner(StringBuilder uri, String scheme, String defAction,
+ String defPackage, int flags) {
if (scheme != null) {
uri.append("scheme=").append(scheme).append(';');
}
- if (mAction != null) {
+ if (mAction != null && !mAction.equals(defAction)) {
uri.append("action=").append(Uri.encode(mAction)).append(';');
}
if (mCategories != null) {
@@ -7155,7 +7365,7 @@ public class Intent implements Parcelable, Cloneable {
if (mFlags != 0) {
uri.append("launchFlags=0x").append(Integer.toHexString(mFlags)).append(';');
}
- if (mPackage != null) {
+ if (mPackage != null && !mPackage.equals(defPackage)) {
uri.append("package=").append(Uri.encode(mPackage)).append(';');
}
if (mComponent != null) {
diff --git a/core/java/android/content/pm/LabeledIntent.aidl b/core/java/android/content/pm/LabeledIntent.aidl
new file mode 100644
index 0000000..ad96759
--- /dev/null
+++ b/core/java/android/content/pm/LabeledIntent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+parcelable LabeledIntent;