diff options
19 files changed, 394 insertions, 47 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 86ed267eafdb..fade5742dc83 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -4838,6 +4838,7 @@ public class Activity extends ContextThemeWrapper * their launch had come from the original activity. * @param intent The Intent to start. * @param options ActivityOptions or null. + * @param permissionToken Token received from the system that permits this call to be made. * @param ignoreTargetSecurity If true, the activity manager will not check whether the * caller it is doing the start is, is actually allowed to start the target activity. * If you set this to true, you must set an explicit component in the Intent and do any @@ -4846,7 +4847,7 @@ public class Activity extends ContextThemeWrapper * @hide */ public void startActivityAsCaller(Intent intent, @Nullable Bundle options, - boolean ignoreTargetSecurity, int userId) { + IBinder permissionToken, boolean ignoreTargetSecurity, int userId) { if (mParent != null) { throw new RuntimeException("Can't be called from a child"); } @@ -4854,7 +4855,7 @@ public class Activity extends ContextThemeWrapper Instrumentation.ActivityResult ar = mInstrumentation.execStartActivityAsCaller( this, mMainThread.getApplicationThread(), mToken, this, - intent, -1, options, ignoreTargetSecurity, userId); + intent, -1, options, permissionToken, ignoreTargetSecurity, userId); if (ar != null) { mMainThread.sendActivityResult( mToken, mEmbeddedID, -1, ar.getResultCode(), diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index b8fe2f13d328..56ccf6f4a76f 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -110,6 +110,32 @@ public class ActivityTaskManager { public static final int RESIZE_MODE_USER_FORCED = RESIZE_MODE_PRESERVE_WINDOW | RESIZE_MODE_FORCED; + /** + * Extra included on intents that are delegating the call to + * ActivityManager#startActivityAsCaller to another app. This token is necessary for that call + * to succeed. Type is IBinder. + * @hide + */ + public static final String EXTRA_PERMISSION_TOKEN = "android.app.extra.PERMISSION_TOKEN"; + + /** + * Extra included on intents that contain an EXTRA_INTENT, with options that the contained + * intent may want to be started with. Type is Bundle. + * TODO: remove once the ChooserActivity moves to systemui + * @hide + */ + public static final String EXTRA_OPTIONS = "android.app.extra.OPTIONS"; + + /** + * Extra included on intents that contain an EXTRA_INTENT, use this boolean value for the + * parameter of the same name when starting the contained intent. + * TODO: remove once the ChooserActivity moves to systemui + * @hide + */ + public static final String EXTRA_IGNORE_TARGET_SECURITY = + "android.app.extra.EXTRA_IGNORE_TARGET_SECURITY"; + + private static int sMaxRecentTasks = -1; ActivityTaskManager(Context context, Handler handler) { diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 6f11a762c6cd..09b77d5b8d0a 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -118,7 +118,7 @@ interface IActivityTaskManager { int startActivityAsCaller(in IApplicationThread caller, in String callingPackage, in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options, - boolean ignoreTargetSecurity, int userId); + IBinder permissionToken, boolean ignoreTargetSecurity, int userId); void unhandledBack(); boolean finishActivity(in IBinder token, int code, in Intent data, int finishTask); @@ -193,6 +193,20 @@ interface IActivityTaskManager { in ActivityManager.TaskDescription description, in Bitmap thumbnail); Point getAppTaskThumbnailSize(); boolean releaseActivityInstance(in IBinder token); + /** + * Only callable from the system. This token grants a temporary permission to call + * #startActivityAsCallerWithToken. The token will time out after + * START_AS_CALLER_TOKEN_TIMEOUT if it is not used. + * + * @param delegatorToken The Binder token referencing the system Activity that wants to delegate + * the #startActivityAsCaller to another app. The "caller" will be the caller of this + * activity's token, not the delegate's caller (which is probably the delegator itself). + * + * @return Returns a token that can be given to a "delegate" app that may call + * #startActivityAsCaller + */ + IBinder requestStartActivityPermissionToken(in IBinder delegatorToken); + void releaseSomeActivities(in IApplicationThread app); Bitmap getTaskDescriptionIcon(in String filename, int userId); void startInPlaceAnimationOnFrontMostApplication(in Bundle opts); diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 1144e26717fe..ac164428abf8 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1902,8 +1902,8 @@ public class Instrumentation { @UnsupportedAppUsage public ActivityResult execStartActivityAsCaller( Context who, IBinder contextThread, IBinder token, Activity target, - Intent intent, int requestCode, Bundle options, boolean ignoreTargetSecurity, - int userId) { + Intent intent, int requestCode, Bundle options, IBinder permissionToken, + boolean ignoreTargetSecurity, int userId) { IApplicationThread whoThread = (IApplicationThread) contextThread; if (mActivityMonitors != null) { synchronized (mSync) { @@ -1934,7 +1934,8 @@ public class Instrumentation { .startActivityAsCaller(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mEmbeddedID : null, - requestCode, 0, null, options, ignoreTargetSecurity, userId); + requestCode, 0, null, options, permissionToken, + ignoreTargetSecurity, userId); checkStartActivityResult(result, intent); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index c31f17af67af..4523d3b2739f 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -16,13 +16,8 @@ package com.android.internal.app; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; -import android.annotation.NonNull; import android.app.Activity; import android.app.ActivityManager; -import android.app.usage.UsageStatsManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -56,7 +51,6 @@ import android.service.chooser.ChooserTargetService; import android.service.chooser.IChooserTargetResult; import android.service.chooser.IChooserTargetService; import android.text.TextUtils; -import android.util.FloatProperty; import android.util.Log; import android.util.Slog; import android.view.LayoutInflater; @@ -66,8 +60,6 @@ import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; import android.widget.AbsListView; import android.widget.BaseAdapter; import android.widget.LinearLayout; @@ -76,10 +68,9 @@ import android.widget.Space; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.app.ResolverActivity; -import com.android.internal.app.ResolverActivity.TargetInfo; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + import com.google.android.collect.Lists; import java.io.File; @@ -847,7 +838,7 @@ public class ChooserActivity extends ResolverActivity { } @Override - public boolean startAsCaller(Activity activity, Bundle options, int userId) { + public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) { final Intent intent = getBaseIntentToSend(); if (intent == null) { return false; @@ -866,8 +857,7 @@ public class ChooserActivity extends ResolverActivity { final boolean ignoreTargetSecurity = mSourceInfo != null && mSourceInfo.getResolvedComponentName().getPackageName() .equals(mChooserTarget.getComponentName().getPackageName()); - activity.startActivityAsCaller(intent, options, ignoreTargetSecurity, userId); - return true; + return activity.startAsCallerImpl(intent, options, ignoreTargetSecurity, userId); } @Override diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 8cfe1c1fc7db..d13bcf2aa186 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -21,7 +21,6 @@ import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; import android.annotation.Nullable; import android.annotation.StringRes; import android.app.Activity; -import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.ActivityThread; import android.app.AppGlobals; @@ -38,7 +37,9 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.Slog; import android.widget.Toast; + import com.android.internal.annotations.VisibleForTesting; + import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -106,7 +107,7 @@ public class IntentForwarderActivity extends Activity { final ResolveInfo ri = mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId); try { - startActivityAsCaller(newIntent, null, false, targetUserId); + startActivityAsCaller(newIntent, null, null, false, targetUserId); } catch (RuntimeException e) { int launchedFromUid = -1; String launchedFromPackage = "?"; diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index fd3891778663..0f8872267042 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -46,6 +46,7 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; +import android.os.IBinder; import android.os.PatternMatcher; import android.os.RemoteException; import android.os.StrictMode; @@ -859,6 +860,37 @@ public class ResolverActivity extends Activity { } } + + boolean startAsCallerImpl(Intent intent, Bundle options, boolean ignoreTargetSecurity, + int userId) { + // Pass intent to delegate chooser activity with permission token. + // TODO: This should move to a trampoline Activity in the system when the ChooserActivity + // moves into systemui + try { + // TODO: Once this is a small springboard activity, it can move off the UI process + // and we can move the request method to ActivityManagerInternal. + IBinder permissionToken = ActivityTaskManager.getService() + .requestStartActivityPermissionToken(getActivityToken()); + final Intent chooserIntent = new Intent(); + final ComponentName delegateActivity = ComponentName.unflattenFromString( + Resources.getSystem().getString(R.string.config_chooserActivity)); + chooserIntent.setClassName(delegateActivity.getPackageName(), + delegateActivity.getClassName()); + chooserIntent.putExtra(ActivityTaskManager.EXTRA_PERMISSION_TOKEN, permissionToken); + + // TODO: These extras will change as chooser activity moves into systemui + chooserIntent.putExtra(Intent.EXTRA_INTENT, intent); + chooserIntent.putExtra(ActivityTaskManager.EXTRA_OPTIONS, options); + chooserIntent.putExtra(ActivityTaskManager.EXTRA_IGNORE_TARGET_SECURITY, + ignoreTargetSecurity); + chooserIntent.putExtra(Intent.EXTRA_USER_ID, userId); + startActivity(chooserIntent); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); + } + return true; + } + public void onActivityStarted(TargetInfo cti) { // Do nothing } @@ -1183,9 +1215,8 @@ public class ResolverActivity extends Activity { } @Override - public boolean startAsCaller(Activity activity, Bundle options, int userId) { - activity.startActivityAsCaller(mResolvedIntent, options, false, userId); - return true; + public boolean startAsCaller(ResolverActivity activity, Bundle options, int userId) { + return activity.startAsCallerImpl(mResolvedIntent, options, false, userId); } @Override @@ -1244,7 +1275,7 @@ public class ResolverActivity extends Activity { * @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller * @return true if the start completed successfully */ - boolean startAsCaller(Activity activity, Bundle options, int userId); + boolean startAsCaller(ResolverActivity activity, Bundle options, int userId); /** * Start the activity referenced by this target as a given user. diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 093a86089aab..297687933018 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2211,6 +2211,12 @@ android:protectionLevel="signature" /> <uses-permission android:name="android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS" /> + <!-- Allows an application to start an activity as another app, provided that app has been + granted a permissionToken from the ActivityManagerService. + @hide --> + <permission android:name="android.permission.START_ACTIVITY_AS_CALLER" + android:protectionLevel="signature" /> + <!-- @deprecated The {@link android.app.ActivityManager#restartPackage} API is no longer supported. --> <permission android:name="android.permission.RESTART_PACKAGES" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index bca72f44bd57..6a355edcf2b3 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2428,7 +2428,10 @@ Can be customized for other product types --> <string name="config_chooseTypeAndAccountActivity" translatable="false" >android/android.accounts.ChooseTypeAndAccountActivity</string> - + <!-- Name of the activity that will handle requests to the system to choose an activity for + the purposes of resolving an intent. --> + <string name="config_chooserActivity" translatable="false" + >com.android.systemui/com.android.systemui.chooser.ChooserActivity</string> <!-- Component name of a custom ResolverActivity (Intent resolver) to be used instead of the default framework version. If left empty, then the framework version will be used. Example: com.google.android.myapp/.resolver.MyResolverActivity --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5418e033aa00..6276884801a4 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1113,6 +1113,7 @@ <java-symbol type="string" name="owner_name" /> <java-symbol type="string" name="config_chooseAccountActivity" /> <java-symbol type="string" name="config_chooseTypeAndAccountActivity" /> + <java-symbol type="string" name="config_chooserActivity" /> <java-symbol type="string" name="config_customResolverActivity" /> <java-symbol type="string" name="config_appsAuthorizedForSharedAccounts" /> <java-symbol type="string" name="error_message_title" /> diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java index a2a40e651f49..cfb6bdd5d613 100644 --- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java @@ -19,6 +19,7 @@ package com.android.internal.app; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; @@ -41,14 +42,14 @@ import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.net.Uri; import android.os.Bundle; +import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.support.test.InstrumentationRegistry; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; -import java.util.ArrayList; -import java.util.List; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -57,6 +58,9 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; +import java.util.List; + @RunWith(AndroidJUnit4.class) public class IntentForwarderActivityTest { @@ -549,8 +553,8 @@ public class IntentForwarderActivityTest { } @Override - public void startActivityAsCaller(Intent intent, @Nullable Bundle options, boolean - ignoreTargetSecurity, int userId) { + public void startActivityAsCaller(Intent intent, @Nullable Bundle options, + IBinder permissionToken, boolean ignoreTargetSecurity, int userId) { mStartActivityIntent = intent; mUserIdActivityLaunchedIn = userId; } @@ -589,4 +593,4 @@ public class IntentForwarderActivityTest { @Override public void showToast(int messageId, int duration) {} } -}
\ No newline at end of file +} diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 2604365ab8dc..a1fbe0a8eaa1 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -391,6 +391,7 @@ applications that come with the platform <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> <permission name="android.permission.REAL_GET_TASKS"/> <permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/> + <permission name="android.permission.START_ACTIVITY_AS_CALLER"/> <permission name="android.permission.START_TASKS_FROM_RECENTS"/> <permission name="android.permission.STATUS_BAR"/> <permission name="android.permission.STOP_APP_SWITCHES"/> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index b2bb88314441..8aced6169f51 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -90,6 +90,7 @@ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.GET_TOP_ACTIVITY_INFO" /> <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" /> + <uses-permission android:name="android.permission.START_ACTIVITY_AS_CALLER" /> <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" /> <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" /> @@ -568,6 +569,22 @@ </intent-filter> </activity> + <activity android:name=".chooser.ChooserActivity" + android:theme="@*android:style/Theme.NoDisplay" + android:finishOnCloseSystemDialogs="true" + android:excludeFromRecents="true" + android:documentLaunchMode="never" + android:relinquishTaskIdentity="true" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" + android:process=":ui" + android:visibleToInstantApps="true"> + <intent-filter> + <action android:name="android.intent.action.CHOOSER_UI" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.VOICE" /> + </intent-filter> + </activity> + <!-- Doze with notifications, run in main sysui process for every user --> <service android:name=".doze.DozeService" diff --git a/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java b/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java new file mode 100644 index 000000000000..158deb46f8d2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/chooser/ChooserActivity.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 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 com.android.systemui.chooser; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +import android.util.Log; + +import com.android.systemui.R; + +import java.lang.Thread; +import java.util.ArrayList; + +/** + * Activity for selecting which application ought to handle an ACTION_SEND intent. + */ +public final class ChooserActivity extends Activity { + + private static final String TAG = "ChooserActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ChooserHelper.onChoose(this); + finish(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java b/packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java new file mode 100644 index 000000000000..a7df6f1cfa67 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/chooser/ChooserHelper.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 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 com.android.systemui.chooser; + +import android.app.Activity; +import android.app.ActivityTaskManager; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; + +/** + * When a target is chosen from the SystemUI Chooser activity, unpack its arguments and + * startActivityAsCaller to handle the now-chosen intent. + */ +public class ChooserHelper { + + private static final String TAG = "ChooserHelper"; + + static void onChoose(Activity activity) { + final Intent thisIntent = activity.getIntent(); + final Bundle thisExtras = thisIntent.getExtras(); + final Intent chosenIntent = thisIntent.getParcelableExtra(Intent.EXTRA_INTENT); + final Bundle options = thisIntent.getParcelableExtra(ActivityTaskManager.EXTRA_OPTIONS); + final IBinder permissionToken = + thisExtras.getBinder(ActivityTaskManager.EXTRA_PERMISSION_TOKEN); + final boolean ignoreTargetSecurity = + thisIntent.getBooleanExtra(ActivityTaskManager.EXTRA_IGNORE_TARGET_SECURITY, false); + final int userId = thisIntent.getIntExtra(Intent.EXTRA_USER_ID, -1); + activity.startActivityAsCaller( + chosenIntent, options, permissionToken, ignoreTargetSecurity, userId); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 5eaee5452ad5..97534edf1cb3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -18,6 +18,7 @@ package com.android.systemui.screenshot; import static android.content.Context.NOTIFICATION_SERVICE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_INTENT; import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_CANCEL_NOTIFICATION; import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP; @@ -35,6 +36,8 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; +import android.content.ClipData; +import android.content.ClipDescription; import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentValues; @@ -66,7 +69,6 @@ import android.util.Slog; import android.view.Display; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.Surface; import android.view.SurfaceControl; import android.view.View; import android.view.ViewGroup; @@ -74,6 +76,7 @@ import android.view.WindowManager; import android.view.animation.Interpolator; import android.widget.ImageView; import android.widget.Toast; + import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.systemui.R; import com.android.systemui.SysUiServiceProvider; @@ -81,6 +84,7 @@ import com.android.systemui.SystemUI; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.util.NotificationChannels; + import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; @@ -290,6 +294,12 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Intent sharingIntent = new Intent(Intent.ACTION_SEND); sharingIntent.setType("image/png"); sharingIntent.putExtra(Intent.EXTRA_STREAM, uri); + // Include URI in ClipData also, so that grantPermission picks it up. + // We don't use setData here because some apps interpret this as "to:". + ClipData clipdata = new ClipData(new ClipDescription("content", + new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}), + new ClipData.Item(uri)); + sharingIntent.setClipData(clipdata); sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject); sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); @@ -298,7 +308,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); Intent sharingChooserIntent = Intent.createChooser(sharingIntent, null, chooserAction.getIntentSender()) - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // Create a share action for the notification PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, 0, diff --git a/packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java new file mode 100644 index 000000000000..19ad7ca64577 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/chooser/ChooserHelperTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 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 com.android.systemui.chooser; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.app.ActivityTaskManager; +import android.content.Intent; +import android.os.Binder; +import android.support.test.runner.AndroidJUnit4; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ChooserHelperTest extends SysuiTestCase { + + @Test + public void testOnChoose_CallsStartActivityAsCallerWithToken() { + final Intent intent = new Intent(); + final Binder token = new Binder(); + intent.putExtra(ActivityTaskManager.EXTRA_PERMISSION_TOKEN, token); + + final Activity mockActivity = mock(Activity.class); + when(mockActivity.getIntent()).thenReturn(intent); + + ChooserHelper.onChoose(mockActivity); + verify(mockActivity, times(1)).startActivityAsCaller( + any(), any(), eq(token), anyBoolean(), anyInt()); + } +} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c645e52a33fc..e90d42fa2dce 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1336,7 +1336,7 @@ public class ActivityManagerService extends IActivityManager.Stub IApplicationThread thread) { if (DEBUG_ALL) Slog.v( TAG, "New death recipient " + this - + " for thread " + thread.asBinder()); + + " for thread " + thread.asBinder()); mApp = app; mPid = pid; mAppThread = thread; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 703c6f18c721..2e75c36e6a6f 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -62,6 +62,7 @@ import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL; import static android.provider.Settings.Global.HIDE_ERROR_DIALOGS; import static android.provider.Settings.System.FONT_SCALE; import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION; +import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.TRANSIT_NONE; @@ -278,6 +279,7 @@ import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -395,6 +397,30 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { // How long to wait in getAutofillAssistStructure() for the activity to respond with the result. private static final int PENDING_AUTOFILL_ASSIST_STRUCTURE_TIMEOUT = 2000; + // Permission tokens are used to temporarily granted a trusted app the ability to call + // #startActivityAsCaller. A client is expected to dump its token after this time has elapsed, + // showing any appropriate error messages to the user. + private static final long START_AS_CALLER_TOKEN_TIMEOUT = + 10 * MINUTE_IN_MILLIS; + + // How long before the service actually expires a token. This is slightly longer than + // START_AS_CALLER_TOKEN_TIMEOUT, to provide a buffer so clients will rarely encounter the + // expiration exception. + private static final long START_AS_CALLER_TOKEN_TIMEOUT_IMPL = + START_AS_CALLER_TOKEN_TIMEOUT + 2 * 1000; + + // How long the service will remember expired tokens, for the purpose of providing error + // messaging when a client uses an expired token. + private static final long START_AS_CALLER_TOKEN_EXPIRED_TIMEOUT = + START_AS_CALLER_TOKEN_TIMEOUT_IMPL + 20 * MINUTE_IN_MILLIS; + + // Activity tokens of system activities that are delegating their call to + // #startActivityByCaller, keyed by the permissionToken granted to the delegate. + final HashMap<IBinder, IBinder> mStartActivitySources = new HashMap<>(); + + // Permission tokens that have expired, but we remember for error reporting. + final ArrayList<IBinder> mExpiredStartAsCallerTokens = new ArrayList<>(); + private final ArrayList<PendingAssistExtras> mPendingAssistExtras = new ArrayList<>(); // Keeps track of the active voice interaction service component, notified from @@ -1143,16 +1169,43 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + + @Override + public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) { + int callingUid = Binder.getCallingUid(); + if (UserHandle.getAppId(callingUid) != SYSTEM_UID) { + throw new SecurityException("Only the system process can request a permission token, " + + "received request from uid: " + callingUid); + } + IBinder permissionToken = new Binder(); + synchronized (mGlobalLock) { + mStartActivitySources.put(permissionToken, delegatorToken); + } + + Message expireMsg = PooledLambda.obtainMessage( + ActivityTaskManagerService::expireStartAsCallerTokenMsg, this, permissionToken); + mUiHandler.sendMessageDelayed(expireMsg, START_AS_CALLER_TOKEN_TIMEOUT_IMPL); + + Message forgetMsg = PooledLambda.obtainMessage( + ActivityTaskManagerService::forgetStartAsCallerTokenMsg, this, permissionToken); + mUiHandler.sendMessageDelayed(forgetMsg, START_AS_CALLER_TOKEN_EXPIRED_TIMEOUT); + + return permissionToken; + } + @Override public final int startActivityAsCaller(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, - int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, boolean ignoreTargetSecurity, - int userId) { - + int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, IBinder permissionToken, + boolean ignoreTargetSecurity, int userId) { // This is very dangerous -- it allows you to perform a start activity (including - // permission grants) as any app that may launch one of your own activities. So - // we will only allow this to be done from activities that are part of the core framework, - // and then only when they are running as the system. + // permission grants) as any app that may launch one of your own activities. So we only + // allow this in two cases: + // 1) The caller is an activity that is part of the core framework, and then only when it + // is running as the system. + // 2) The caller provides a valid permissionToken. Permission tokens are one-time use and + // can only be requested by a system activity, which may then delegate this call to + // another app. final ActivityRecord sourceRecord; final int targetUid; final String targetPackage; @@ -1161,17 +1214,46 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (resultTo == null) { throw new SecurityException("Must be called from an activity"); } - sourceRecord = mStackSupervisor.isInAnyStackLocked(resultTo); - if (sourceRecord == null) { - throw new SecurityException("Called with bad activity token: " + resultTo); + final IBinder sourceToken; + if (permissionToken != null) { + // To even attempt to use a permissionToken, an app must also have this signature + // permission. + mAmInternal.enforceCallingPermission( + android.Manifest.permission.START_ACTIVITY_AS_CALLER, + "startActivityAsCaller"); + // If called with a permissionToken, we want the sourceRecord from the delegator + // activity that requested this token. + sourceToken = mStartActivitySources.remove(permissionToken); + if (sourceToken == null) { + // Invalid permissionToken, check if it recently expired. + if (mExpiredStartAsCallerTokens.contains(permissionToken)) { + throw new SecurityException("Called with expired permission token: " + + permissionToken); + } else { + throw new SecurityException("Called with invalid permission token: " + + permissionToken); + } + } + } else { + // This method was called directly by the source. + sourceToken = resultTo; } - if (!sourceRecord.info.packageName.equals("android")) { - throw new SecurityException( - "Must be called from an activity that is declared in the android package"); + + sourceRecord = mStackSupervisor.isInAnyStackLocked(sourceToken); + if (sourceRecord == null) { + throw new SecurityException("Called with bad activity token: " + sourceToken); } if (sourceRecord.app == null) { throw new SecurityException("Called without a process attached to activity"); } + + // Whether called directly or from a delegate, the source activity must be from the + // android package. + if (!sourceRecord.info.packageName.equals("android")) { + throw new SecurityException("Must be called from an activity that is " + + "declared in the android package"); + } + if (UserHandle.getAppId(sourceRecord.app.mUid) != SYSTEM_UID) { // This is still okay, as long as this activity is running under the // uid of the original calling activity. @@ -4901,6 +4983,15 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + private void expireStartAsCallerTokenMsg(IBinder permissionToken) { + mStartActivitySources.remove(permissionToken); + mExpiredStartAsCallerTokens.add(permissionToken); + } + + private void forgetStartAsCallerTokenMsg(IBinder permissionToken) { + mExpiredStartAsCallerTokens.remove(permissionToken); + } + boolean isActivityStartsLoggingEnabled() { return mAmInternal.isActivityStartsLoggingEnabled(); } @@ -5452,6 +5543,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final class H extends Handler { static final int REPORT_TIME_TRACKER_MSG = 1; + + static final int FIRST_ACTIVITY_STACK_MSG = 100; static final int FIRST_SUPERVISOR_STACK_MSG = 200; @@ -6759,4 +6852,4 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } } -} +}
\ No newline at end of file |