Merge "Finish autofill integration with keyboard cleanup tasks"
diff --git a/api/current.txt b/api/current.txt
index fca72b1..9425b81 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -91,6 +91,7 @@
field public static final String KILL_BACKGROUND_PROCESSES = "android.permission.KILL_BACKGROUND_PROCESSES";
field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
+ field public static final String MANAGE_EXTERNAL_STORAGE = "android.permission.MANAGE_EXTERNAL_STORAGE";
field public static final String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS";
field public static final String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
field public static final String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL";
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index b5ac37b..c942a46 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1219,7 +1219,8 @@
OP_START_FOREGROUND,
OP_SMS_FINANCIAL_TRANSACTIONS,
OP_MANAGE_IPSEC_TUNNELS,
- OP_INSTANT_APP_START_FOREGROUND
+ OP_INSTANT_APP_START_FOREGROUND,
+ OP_MANAGE_EXTERNAL_STORAGE,
};
/**
@@ -1623,7 +1624,7 @@
null, // no direct permission for OP_READ_DEVICE_IDENTIFIERS
Manifest.permission.ACCESS_MEDIA_LOCATION,
null, // no permission for OP_QUERY_ALL_PACKAGES
- null, // no permission for OP_MANAGE_EXTERNAL_STORAGE
+ Manifest.permission.MANAGE_EXTERNAL_STORAGE,
};
/**
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index 2138d553..63335a0 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -38,6 +38,7 @@
import android.content.pm.DataLoaderParams;
import android.content.pm.InstallationFile;
import android.os.IVold;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.ArraySet;
@@ -208,7 +209,8 @@
if (!hasObb()) {
return;
}
- final String mainObbDir = String.format("/storage/emulated/0/Android/obb/%s", mPackageName);
+ final String obbDir = "/storage/emulated/0/Android/obb";
+ final String packageObbDir = String.format("%s/%s", obbDir, mPackageName);
final String packageObbDirRoot =
String.format("/mnt/runtime/%s/emulated/0/Android/obb/", mPackageName);
final String[] obbDirs = {
@@ -217,12 +219,12 @@
packageObbDirRoot + "full",
packageObbDirRoot + "default",
String.format("/data/media/0/Android/obb/%s", mPackageName),
- mainObbDir,
+ packageObbDir,
};
try {
- Slog.i(TAG, "Creating obb directory '" + mainObbDir + "'");
+ Slog.i(TAG, "Creating obb directory '" + packageObbDir + "'");
final IVold vold = IVold.Stub.asInterface(ServiceManager.getServiceOrThrow("vold"));
- vold.mkdirs(mainObbDir);
+ vold.setupAppDir(packageObbDir, obbDir, Process.ROOT_UID);
for (String d : obbDirs) {
mObbStorage.bindPermanent(d);
}
@@ -230,7 +232,7 @@
Slog.e(TAG, "vold service is not found.");
cleanUp();
} catch (IOException | RemoteException ex) {
- Slog.e(TAG, "Failed to create obb dir at: " + mainObbDir, ex);
+ Slog.e(TAG, "Failed to create obb dir at: " + packageObbDir, ex);
cleanUp();
}
}
diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
index 33aa665..f9ba34e 100644
--- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
@@ -15,14 +15,20 @@
*/
package com.android.internal.app;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ShortcutType;
+
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.view.LayoutInflater;
@@ -30,12 +36,17 @@
import android.view.ViewGroup;
import android.view.Window;
import android.view.accessibility.AccessibilityManager;
+import android.widget.AdapterView;
import android.widget.BaseAdapter;
+import android.widget.FrameLayout;
import android.widget.ImageView;
+import android.widget.Switch;
import android.widget.TextView;
import com.android.internal.R;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -48,40 +59,74 @@
private static final String MAGNIFICATION_COMPONENT_ID =
"com.android.server.accessibility.MagnificationController";
- private AccessibilityButtonTarget mMagnificationTarget = null;
-
- private List<AccessibilityButtonTarget> mTargets = null;
-
+ private int mShortcutType;
+ private List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
+ private List<AccessibilityButtonTarget> mReadyToBeDisabledTargets = new ArrayList<>();
private AlertDialog mAlertDialog;
+ private TargetAdapter mTargetAdapter;
+
+ /**
+ * Annotation for different accessibilityService fragment UI type.
+ *
+ * {@code LEGACY} for displaying appearance aligned with sdk version Q accessibility service
+ * page, but only hardware shortcut allowed and under service in version Q or early.
+ * {@code INVISIBLE} for displaying appearance without switch bar.
+ * {@code INTUITIVE} for displaying appearance with version R accessibility design.
+ * {@code BOUNCE} for displaying appearance with pop-up action.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ AccessibilityServiceFragmentType.LEGACY,
+ AccessibilityServiceFragmentType.INVISIBLE,
+ AccessibilityServiceFragmentType.INTUITIVE,
+ AccessibilityServiceFragmentType.BOUNCE,
+ })
+ public @interface AccessibilityServiceFragmentType {
+ int LEGACY = 0;
+ int INVISIBLE = 1;
+ int INTUITIVE = 2;
+ int BOUNCE = 3;
+ }
+
+ /**
+ * Annotation for different shortcut menu mode.
+ *
+ * {@code LAUNCH} for clicking list item to trigger the service callback.
+ * {@code EDIT} for clicking list item and save button to disable the service.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ ShortcutMenuMode.LAUNCH,
+ ShortcutMenuMode.EDIT,
+ })
+ public @interface ShortcutMenuMode {
+ int LAUNCH = 0;
+ int EDIT = 1;
+ }
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final TypedArray theme = getTheme().obtainStyledAttributes(android.R.styleable.Theme);
- if (!theme.getBoolean(android.R.styleable.Theme_windowNoTitle, /* defValue= */false)) {
+ if (!theme.getBoolean(android.R.styleable.Theme_windowNoTitle, /* defValue= */ false)) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
}
- // TODO(b/146815874): Will replace it with white list services
- mMagnificationTarget = new AccessibilityButtonTarget(this, MAGNIFICATION_COMPONENT_ID,
- R.string.accessibility_magnification_chooser_text,
- R.drawable.ic_accessibility_magnification);
-
- // TODO(b/146815544): Will use shortcut type or button type to get the corresponding
- // services
- mTargets = getServiceAccessibilityButtonTargets(this);
- if (Settings.Secure.getInt(getContentResolver(),
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1) {
- mTargets.add(mMagnificationTarget);
- }
+ mShortcutType = getIntent().getIntExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE,
+ ACCESSIBILITY_BUTTON);
+ mTargets.addAll(getServiceTargets(this, mShortcutType));
// TODO(b/146815548): Will add title to separate which one type
+ mTargetAdapter = new TargetAdapter(mTargets);
mAlertDialog = new AlertDialog.Builder(this)
- .setAdapter(new TargetAdapter(),
- (dialog, position) -> onTargetSelected(mTargets.get(position)))
+ .setAdapter(mTargetAdapter, /* listener= */ null)
+ .setPositiveButton(
+ getString(R.string.edit_accessibility_shortcut_menu_button),
+ /* listener= */ null)
.setOnDismissListener(dialog -> finish())
.create();
+ mAlertDialog.setOnShowListener(dialog -> updateDialogListeners());
mAlertDialog.show();
}
@@ -91,41 +136,95 @@
super.onDestroy();
}
- private static List<AccessibilityButtonTarget> getServiceAccessibilityButtonTargets(
- @NonNull Context context) {
- AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
+ /**
+ * Gets the corresponding fragment type of a given accessibility service.
+ *
+ * @param accessibilityServiceInfo The accessibilityService's info.
+ * @return int from {@link AccessibilityServiceFragmentType}.
+ */
+ private static @AccessibilityServiceFragmentType int getAccessibilityServiceFragmentType(
+ AccessibilityServiceInfo accessibilityServiceInfo) {
+ final int targetSdk = accessibilityServiceInfo.getResolveInfo()
+ .serviceInfo.applicationInfo.targetSdkVersion;
+ final boolean requestA11yButton = (accessibilityServiceInfo.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+
+ if (targetSdk <= Build.VERSION_CODES.Q) {
+ return AccessibilityServiceFragmentType.LEGACY;
+ }
+ return requestA11yButton
+ ? AccessibilityServiceFragmentType.INVISIBLE
+ : AccessibilityServiceFragmentType.INTUITIVE;
+ }
+
+ private static List<AccessibilityButtonTarget> getServiceTargets(@NonNull Context context,
+ @ShortcutType int shortcutType) {
+ final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
Context.ACCESSIBILITY_SERVICE);
- List<AccessibilityServiceInfo> services = ams.getEnabledAccessibilityServiceList(
- AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
- if (services == null) {
+ final List<AccessibilityServiceInfo> installedServices =
+ ams.getInstalledAccessibilityServiceList();
+ if (installedServices == null) {
return Collections.emptyList();
}
- ArrayList<AccessibilityButtonTarget> targets = new ArrayList<>(services.size());
- for (AccessibilityServiceInfo info : services) {
+ final List<AccessibilityButtonTarget> targets = new ArrayList<>(installedServices.size());
+ for (AccessibilityServiceInfo info : installedServices) {
if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
targets.add(new AccessibilityButtonTarget(context, info));
}
}
+ final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType);
+ targets.removeIf(target -> !requiredTargets.contains(target.getId()));
+
+ // TODO(b/146815874): Will replace it with white list services.
+ if (Settings.Secure.getInt(context.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1) {
+ final AccessibilityButtonTarget magnificationTarget = new AccessibilityButtonTarget(
+ context,
+ MAGNIFICATION_COMPONENT_ID,
+ R.string.accessibility_magnification_chooser_text,
+ R.drawable.ic_accessibility_magnification,
+ AccessibilityServiceFragmentType.INTUITIVE);
+ targets.add(magnificationTarget);
+ }
+
return targets;
}
- private void onTargetSelected(AccessibilityButtonTarget target) {
- Settings.Secure.putString(getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, target.getId());
- finish();
+ private static class ViewHolder {
+ ImageView mIconView;
+ TextView mLabelView;
+ FrameLayout mItemContainer;
+ ImageView mViewItem;
+ Switch mSwitchItem;
}
- private class TargetAdapter extends BaseAdapter {
+ private static class TargetAdapter extends BaseAdapter {
+ @ShortcutMenuMode
+ private int mShortcutMenuMode = ShortcutMenuMode.LAUNCH;
+ private List<AccessibilityButtonTarget> mButtonTargets;
+
+ TargetAdapter(List<AccessibilityButtonTarget> targets) {
+ this.mButtonTargets = targets;
+ }
+
+ void setShortcutMenuMode(int shortcutMenuMode) {
+ mShortcutMenuMode = shortcutMenuMode;
+ }
+
+ int getShortcutMenuMode() {
+ return mShortcutMenuMode;
+ }
+
@Override
public int getCount() {
- return mTargets.size();
+ return mButtonTargets.size();
}
@Override
public Object getItem(int position) {
- return null;
+ return mButtonTargets.get(position);
}
@Override
@@ -135,36 +234,110 @@
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- LayoutInflater inflater = AccessibilityButtonChooserActivity.this.getLayoutInflater();
- View root = inflater.inflate(R.layout.accessibility_button_chooser_item, parent, false);
- final AccessibilityButtonTarget target = mTargets.get(position);
- ImageView iconView = root.findViewById(R.id.accessibility_button_target_icon);
- TextView labelView = root.findViewById(R.id.accessibility_button_target_label);
- iconView.setImageDrawable(target.getDrawable());
- labelView.setText(target.getLabel());
+ final Context context = parent.getContext();
+ ViewHolder holder;
+ if (convertView == null) {
+ convertView = LayoutInflater.from(context).inflate(
+ R.layout.accessibility_button_chooser_item, parent, /* attachToRoot= */
+ false);
+ holder = new ViewHolder();
+ holder.mIconView = convertView.findViewById(R.id.accessibility_button_target_icon);
+ holder.mLabelView = convertView.findViewById(
+ R.id.accessibility_button_target_label);
+ holder.mItemContainer = convertView.findViewById(
+ R.id.accessibility_button_target_item_container);
+ holder.mViewItem = convertView.findViewById(
+ R.id.accessibility_button_target_view_item);
+ holder.mSwitchItem = convertView.findViewById(
+ R.id.accessibility_button_target_switch_item);
+ convertView.setTag(holder);
+ } else {
+ holder = (ViewHolder) convertView.getTag();
+ }
- // TODO(b/146815874): Need to get every service status to update UI
- return root;
+ final AccessibilityButtonTarget target = mButtonTargets.get(position);
+ holder.mIconView.setImageDrawable(target.getDrawable());
+ holder.mLabelView.setText(target.getLabel());
+
+ updateActionItem(context, holder, target);
+
+ return convertView;
+ }
+
+ private void updateActionItem(@NonNull Context context,
+ @NonNull ViewHolder holder, AccessibilityButtonTarget target) {
+
+ switch (target.getFragmentType()) {
+ case AccessibilityServiceFragmentType.LEGACY:
+ case AccessibilityServiceFragmentType.INVISIBLE:
+ updateLegacyOrInvisibleActionItemVisibility(context, holder);
+ break;
+ case AccessibilityServiceFragmentType.INTUITIVE:
+ updateIntuitiveActionItemVisibility(context, holder, target);
+ break;
+ case AccessibilityServiceFragmentType.BOUNCE:
+ updateBounceActionItemVisibility(context, holder);
+ break;
+ default:
+ throw new IllegalStateException("Unexpected fragment type");
+ }
+ }
+
+ private void updateLegacyOrInvisibleActionItemVisibility(@NonNull Context context,
+ @NonNull ViewHolder holder) {
+ final boolean isEditMenuMode = mShortcutMenuMode == ShortcutMenuMode.EDIT;
+
+ holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
+ holder.mViewItem.setVisibility(View.VISIBLE);
+ holder.mSwitchItem.setVisibility(View.GONE);
+ holder.mItemContainer.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
+ }
+
+ private void updateIntuitiveActionItemVisibility(@NonNull Context context,
+ @NonNull ViewHolder holder, AccessibilityButtonTarget target) {
+ final boolean isEditMenuMode = mShortcutMenuMode == ShortcutMenuMode.EDIT;
+
+ holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
+ holder.mViewItem.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
+ holder.mSwitchItem.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE);
+ holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled(context, target));
+ holder.mItemContainer.setVisibility(View.VISIBLE);
+ }
+
+ private void updateBounceActionItemVisibility(@NonNull Context context,
+ @NonNull ViewHolder holder) {
+ final boolean isEditMenuMode = mShortcutMenuMode == ShortcutMenuMode.EDIT;
+
+ holder.mViewItem.setImageDrawable(
+ isEditMenuMode ? context.getDrawable(R.drawable.ic_delete_item)
+ : context.getDrawable(R.drawable.ic_open_in_new));
+ holder.mViewItem.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
+ holder.mSwitchItem.setVisibility(View.GONE);
+ holder.mItemContainer.setVisibility(View.VISIBLE);
}
}
private static class AccessibilityButtonTarget {
- public String mId;
- public CharSequence mLabel;
- public Drawable mDrawable;
- // TODO(b/146815874): Will add fragment type and related functions
- public AccessibilityButtonTarget(@NonNull Context context,
+ private String mId;
+ private CharSequence mLabel;
+ private Drawable mDrawable;
+ @AccessibilityServiceFragmentType
+ private int mFragmentType;
+
+ AccessibilityButtonTarget(@NonNull Context context,
@NonNull AccessibilityServiceInfo serviceInfo) {
this.mId = serviceInfo.getComponentName().flattenToString();
this.mLabel = serviceInfo.getResolveInfo().loadLabel(context.getPackageManager());
this.mDrawable = serviceInfo.getResolveInfo().loadIcon(context.getPackageManager());
+ this.mFragmentType = getAccessibilityServiceFragmentType(serviceInfo);
}
- public AccessibilityButtonTarget(Context context, @NonNull String id, int labelResId,
- int iconRes) {
+ AccessibilityButtonTarget(Context context, @NonNull String id, int labelResId,
+ int iconRes, @AccessibilityServiceFragmentType int fragmentType) {
this.mId = id;
this.mLabel = context.getText(labelResId);
this.mDrawable = context.getDrawable(iconRes);
+ this.mFragmentType = fragmentType;
}
public String getId() {
@@ -178,5 +351,105 @@
public Drawable getDrawable() {
return mDrawable;
}
+
+ public int getFragmentType() {
+ return mFragmentType;
+ }
+ }
+
+ private static boolean isServiceEnabled(@NonNull Context context,
+ AccessibilityButtonTarget target) {
+ final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ final List<AccessibilityServiceInfo> enabledServices =
+ ams.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+
+ for (AccessibilityServiceInfo info : enabledServices) {
+ final String id = info.getComponentName().flattenToString();
+ if (id.equals(target.getId())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) {
+ Settings.Secure.putString(getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+ mTargets.get(position).getId());
+ // TODO(b/146969684): notify accessibility button clicked.
+ mAlertDialog.dismiss();
+ }
+
+ private void onTargetDeleted(AdapterView<?> parent, View view, int position, long id) {
+ // TODO(b/147027236): Will discuss with UX designer what UX behavior about deleting item
+ // is good for user.
+ mReadyToBeDisabledTargets.add(mTargets.get(position));
+ mTargets.remove(position);
+ mTargetAdapter.notifyDataSetChanged();
+ }
+
+ private void onCancelButtonClicked() {
+ resetAndUpdateTargets();
+
+ mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH);
+ mTargetAdapter.notifyDataSetChanged();
+
+ mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
+ mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
+ getString(R.string.edit_accessibility_shortcut_menu_button));
+
+ updateDialogListeners();
+ }
+
+ private void onEditButtonClicked() {
+ mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.EDIT);
+ mTargetAdapter.notifyDataSetChanged();
+
+ mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setText(
+ getString(R.string.cancel_accessibility_shortcut_menu_button));
+ mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.VISIBLE);
+ mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
+ getString(R.string.save_accessibility_shortcut_menu_button));
+
+ updateDialogListeners();
+ }
+
+ private void onSaveButtonClicked() {
+ disableTargets();
+ resetAndUpdateTargets();
+
+ mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH);
+ mTargetAdapter.notifyDataSetChanged();
+
+ mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
+ mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
+ getString(R.string.edit_accessibility_shortcut_menu_button));
+
+ updateDialogListeners();
+ }
+
+ private void updateDialogListeners() {
+ final boolean isEditMenuMode =
+ mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT;
+
+ mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(
+ view -> onCancelButtonClicked());
+ mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(
+ isEditMenuMode ? view -> onSaveButtonClicked() : view -> onEditButtonClicked());
+ mAlertDialog.getListView().setOnItemClickListener(
+ isEditMenuMode ? this::onTargetDeleted : this::onTargetSelected);
+ }
+
+ private void disableTargets() {
+ for (AccessibilityButtonTarget service : mReadyToBeDisabledTargets) {
+ // TODO(b/146967898): disable services.
+ }
+ }
+
+ private void resetAndUpdateTargets() {
+ mTargets.clear();
+ mTargets.addAll(getServiceTargets(this, mShortcutType));
}
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5410cf5..405f3da 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -932,6 +932,14 @@
<permission android:name="android.permission.WRITE_OBB"
android:protectionLevel="signature|privileged" />
+ <!-- Allows an application a broad access to external storage in scoped storage.
+ Intended to be used by few apps that need to manage files on behalf of the users.
+ <p>Protection level: signature|appop
+ <p>This protection level is temporary and will most likely be changed to |preinstalled -->
+ <permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:protectionLevel="signature|appop" />
+
<!-- ====================================================================== -->
<!-- Permissions for accessing the device location -->
<!-- ====================================================================== -->
diff --git a/core/res/res/drawable/ic_delete_item.xml b/core/res/res/drawable/ic_delete_item.xml
new file mode 100644
index 0000000..8a398a4
--- /dev/null
+++ b/core/res/res/drawable/ic_delete_item.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2020 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/core/res/res/drawable/ic_open_in_new.xml b/core/res/res/drawable/ic_open_in_new.xml
new file mode 100644
index 0000000..67378c8
--- /dev/null
+++ b/core/res/res/drawable/ic_open_in_new.xml
@@ -0,0 +1,26 @@
+<!--
+ Copyright (C) 2020 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:pathData="M19,19H5V5h7V3H5c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2v-7h-2v7zM14,3v2h3.59l-9.83,9.83 1.41,1.41L19,6.41V10h2V3h-7z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/core/res/res/layout/accessibility_button_chooser_item.xml b/core/res/res/layout/accessibility_button_chooser_item.xml
index fddca5a..1edd2cd 100644
--- a/core/res/res/layout/accessibility_button_chooser_item.xml
+++ b/core/res/res/layout/accessibility_button_chooser_item.xml
@@ -21,10 +21,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
- android:paddingBottom="12dp"
- android:paddingEnd="16dp"
android:paddingStart="16dp"
- android:paddingTop="12dp">
+ android:paddingEnd="16dp"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp">
<ImageView
android:id="@+id/accessibility_button_target_icon"
@@ -39,5 +39,26 @@
android:layout_marginStart="8dp"
android:layout_weight="1"
android:textColor="?attr/textColorPrimary"/>
+
+ <FrameLayout
+ android:id="@+id/accessibility_button_target_item_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="56dp">
+
+ <ImageView
+ android:id="@+id/accessibility_button_target_view_item"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+
+ <Switch android:id="@+id/accessibility_button_target_switch_item"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:background="@null"
+ android:clickable="false"
+ android:focusable="false"/>
+ </FrameLayout>
</LinearLayout>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ed744ba..66267d1 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4354,6 +4354,18 @@
You can change the feature in Settings > Accessibility.
</string>
+ <!-- Text in button that edit the accessibility shortcut menu. [CHAR LIMIT=100] -->
+ <string name="accessibility_shortcut_menu_button">Empty</string>
+
+ <!-- Text in button that edit the accessibility shortcut menu. [CHAR LIMIT=100] -->
+ <string name="edit_accessibility_shortcut_menu_button">Edit</string>
+
+ <!-- Text in button that save the accessibility shortcut menu changed status. [CHAR LIMIT=100] -->
+ <string name="save_accessibility_shortcut_menu_button">Save</string>
+
+ <!-- Text in button that cancel the accessibility shortcut menu changed status. [CHAR LIMIT=100] -->
+ <string name="cancel_accessibility_shortcut_menu_button">Cancel</string>
+
<!-- Text in button that turns off the accessibility shortcut -->
<string name="disable_accessibility_shortcut">Turn off Shortcut</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a00296c..356ec2b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3207,10 +3207,19 @@
<java-symbol type="layout" name="accessibility_button_chooser_item" />
<java-symbol type="id" name="accessibility_button_target_icon" />
<java-symbol type="id" name="accessibility_button_target_label" />
+ <java-symbol type="id" name="accessibility_button_target_item_container" />
+ <java-symbol type="id" name="accessibility_button_target_view_item" />
+ <java-symbol type="id" name="accessibility_button_target_switch_item" />
<java-symbol type="string" name="accessibility_magnification_chooser_text" />
+ <java-symbol type="string" name="edit_accessibility_shortcut_menu_button" />
+ <java-symbol type="string" name="save_accessibility_shortcut_menu_button" />
+ <java-symbol type="string" name="cancel_accessibility_shortcut_menu_button" />
<java-symbol type="drawable" name="ic_accessibility_magnification" />
+ <java-symbol type="drawable" name="ic_delete_item" />
+ <java-symbol type="drawable" name="ic_open_in_new" />
+
<!-- com.android.internal.widget.RecyclerView -->
<java-symbol type="id" name="item_touch_helper_previous_elevation"/>
<java-symbol type="dimen" name="item_touch_helper_max_drag_scroll_per_frame"/>
diff --git a/packages/SettingsLib/res/values/arrays.xml b/packages/SettingsLib/res/values/arrays.xml
index 9b4a6d6..7b589370 100644
--- a/packages/SettingsLib/res/values/arrays.xml
+++ b/packages/SettingsLib/res/values/arrays.xml
@@ -132,6 +132,20 @@
<item>avrcp16</item>
</string-array>
+ <!-- Titles for Bluetooth MAP Versions -->
+ <string-array name="bluetooth_map_versions">
+ <item>MAP 1.2 (Default)</item>
+ <item>MAP 1.3</item>
+ <item>MAP 1.4</item>
+ </string-array>
+
+ <!-- Values for Bluetooth MAP Versions -->
+ <string-array name="bluetooth_map_version_values">
+ <item>map12</item>
+ <item>map13</item>
+ <item>map14</item>
+ </string-array>
+
<!-- Titles for Bluetooth Audio Codec selection preference. [CHAR LIMIT=50] -->
<string-array name="bluetooth_a2dp_codec_titles">
<item>Use System Selection (Default)</item>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 96f307d..a7df6db 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -606,6 +606,11 @@
<!-- UI debug setting: Select Bluetooth AVRCP Version -->
<string name="bluetooth_select_avrcp_version_dialog_title">Select Bluetooth AVRCP Version</string>
+ <!-- UI debug setting: Select Bluetooth MAP Version [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_select_map_version_string">Bluetooth MAP Version</string>
+ <!-- UI debug setting: Select Bluetooth MAP Version [CHAR LIMIT=NONE] -->
+ <string name="bluetooth_select_map_version_dialog_title">Select Bluetooth MAP Version</string>
+
<!-- UI debug setting: Trigger Bluetooth Audio Codec Selection -->
<string name="bluetooth_select_a2dp_codec_type">Bluetooth Audio Codec</string>
<!-- UI debug setting: Trigger Bluetooth Audio Codec Selection -->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 5abca6b..7ad07c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -168,7 +168,8 @@
mStatusBarStateController = statusBarStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mDockManager = dockManager;
- mDockManager.addAlignmentStateListener(this::handleAlignStateChanged);
+ mDockManager.addAlignmentStateListener(
+ alignState -> mHandler.post(() -> handleAlignStateChanged(alignState)));
// lock icon is not used on all form factors.
if (mLockIcon != null) {
mLockIcon.setOnLongClickListener(this::handleLockLongClick);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 48169ea..0a3bc6d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -154,12 +154,14 @@
@Test
public void onAlignmentStateChanged_showsSlowChargingIndication() {
- createController();
- verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
- mController.setVisible(true);
+ mInstrumentation.runOnMainSync(() -> {
+ createController();
+ verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
+ mController.setVisible(true);
- mAlignmentListener.getValue().onAlignmentStateChanged(
- DockManager.ALIGN_STATE_POOR);
+ mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_POOR);
+ });
+ mInstrumentation.waitForIdleSync();
assertThat(mTextView.getText()).isEqualTo(
mContext.getResources().getString(R.string.dock_alignment_slow_charging));
@@ -169,11 +171,14 @@
@Test
public void onAlignmentStateChanged_showsNotChargingIndication() {
- createController();
- verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
- mController.setVisible(true);
+ mInstrumentation.runOnMainSync(() -> {
+ createController();
+ verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
+ mController.setVisible(true);
- mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_TERRIBLE);
+ mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_TERRIBLE);
+ });
+ mInstrumentation.waitForIdleSync();
assertThat(mTextView.getText()).isEqualTo(
mContext.getResources().getString(R.string.dock_alignment_not_charging));
@@ -183,13 +188,15 @@
@Test
public void onAlignmentStateChanged_whileDozing_showsSlowChargingIndication() {
- createController();
- verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
- mController.setVisible(true);
- mController.setDozing(true);
+ mInstrumentation.runOnMainSync(() -> {
+ createController();
+ verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
+ mController.setVisible(true);
+ mController.setDozing(true);
- mAlignmentListener.getValue().onAlignmentStateChanged(
- DockManager.ALIGN_STATE_POOR);
+ mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_POOR);
+ });
+ mInstrumentation.waitForIdleSync();
assertThat(mTextView.getText()).isEqualTo(
mContext.getResources().getString(R.string.dock_alignment_slow_charging));
@@ -199,12 +206,15 @@
@Test
public void onAlignmentStateChanged_whileDozing_showsNotChargingIndication() {
- createController();
- verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
- mController.setVisible(true);
- mController.setDozing(true);
+ mInstrumentation.runOnMainSync(() -> {
+ createController();
+ verify(mDockManager).addAlignmentStateListener(mAlignmentListener.capture());
+ mController.setVisible(true);
+ mController.setDozing(true);
- mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_TERRIBLE);
+ mAlignmentListener.getValue().onAlignmentStateChanged(DockManager.ALIGN_STATE_TERRIBLE);
+ });
+ mInstrumentation.waitForIdleSync();
assertThat(mTextView.getText()).isEqualTo(
mContext.getResources().getString(R.string.dock_alignment_not_charging));
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index c474f47..34a8910 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -74,8 +74,6 @@
import android.os.Binder;
import android.os.DropBoxManager;
import android.os.Environment;
-import android.os.Environment.UserEnvironment;
-import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -183,6 +181,8 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
@@ -379,6 +379,14 @@
@GuardedBy("mAppFuseLock")
private AppFuseBridge mAppFuseBridge = null;
+ /** Matches known application dir paths. The first group contains the generic part of the path,
+ * the second group contains the user id (or null if it's a public volume without users), the
+ * third group contains the package name, and the fourth group the remainder of the path.
+ */
+ public static final Pattern KNOWN_APP_DIR_PATHS = Pattern.compile(
+ "(?i)(^/storage/[^/]+/(?:([0-9]+)/)?Android/(?:data|media|obb|sandbox)/)([^/]+)(/.*)?");
+
+
private VolumeInfo findVolumeByIdOrThrow(String id) {
synchronized (mLock) {
final VolumeInfo vol = mVolumes.get(id);
@@ -3135,7 +3143,6 @@
public void mkdirs(String callingPkg, String appPath) {
final int callingUid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(callingUid);
- final UserEnvironment userEnv = new UserEnvironment(userId);
final String propertyName = "sys.user." + userId + ".ce_available";
// Ignore requests to create directories while storage is locked
@@ -3161,25 +3168,36 @@
throw new IllegalStateException("Failed to resolve " + appPath + ": " + e);
}
- // Try translating the app path into a vold path, but require that it
- // belong to the calling package.
- if (FileUtils.contains(userEnv.buildExternalStorageAppDataDirs(callingPkg), appFile) ||
- FileUtils.contains(userEnv.buildExternalStorageAppObbDirs(callingPkg), appFile) ||
- FileUtils.contains(userEnv.buildExternalStorageAppMediaDirs(callingPkg), appFile)) {
- appPath = appFile.getAbsolutePath();
- if (!appPath.endsWith("/")) {
- appPath = appPath + "/";
+ appPath = appFile.getAbsolutePath();
+ if (!appPath.endsWith("/")) {
+ appPath = appPath + "/";
+ }
+ // Ensure that the path we're asked to create is a known application directory
+ // path.
+ final Matcher matcher = KNOWN_APP_DIR_PATHS.matcher(appPath);
+ if (matcher.matches()) {
+ // And that the package dir matches the calling package
+ if (!matcher.group(3).equals(callingPkg)) {
+ throw new SecurityException("Invalid mkdirs path: " + appFile
+ + " does not contain calling package " + callingPkg);
}
-
+ // And that the user id part of the path (if any) matches the calling user id,
+ // or if for a public volume (no user id), the user matches the current user
+ if ((matcher.group(2) != null && !matcher.group(2).equals(Integer.toString(userId)))
+ || (matcher.group(2) == null && userId != mCurrentUserId)) {
+ throw new SecurityException("Invalid mkdirs path: " + appFile
+ + " does not match calling user id " + userId);
+ }
try {
- mVold.mkdirs(appPath);
- return;
- } catch (Exception e) {
+ mVold.setupAppDir(appPath, matcher.group(1), callingUid);
+ } catch (RemoteException e) {
throw new IllegalStateException("Failed to prepare " + appPath + ": " + e);
}
- }
- throw new SecurityException("Invalid mkdirs path: " + appFile);
+ return;
+ }
+ throw new SecurityException("Invalid mkdirs path: " + appFile
+ + " is not a known app path.");
}
@Override
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index 58f6ba2..d729a96 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -954,6 +954,35 @@
onServicePackageRestartedLocked(userId);
}
+ @Override
+ public void onPackageModified(String packageName) {
+ if (verbose) Slog.v(mTag, "onPackageModified(): " + packageName);
+
+ final int userId = getChangingUserId();
+ final String serviceName = mServiceNameResolver.getDefaultServiceName(userId);
+ if (serviceName == null) {
+ return;
+ }
+
+ final ComponentName serviceComponentName =
+ ComponentName.unflattenFromString(serviceName);
+ if (serviceComponentName == null
+ || !serviceComponentName.getPackageName().equals(packageName)) {
+ return;
+ }
+
+ // The default service package has changed, update the cached if the service
+ // exists but no active component.
+ final S service = peekServiceForUserLocked(userId);
+ if (service != null) {
+ final ComponentName componentName = service.getServiceComponentName();
+ if (componentName == null) {
+ if (verbose) Slog.v(mTag, "update cached");
+ updateCachedServiceLocked(userId);
+ }
+ }
+ }
+
private String getActiveServicePackageNameLocked() {
final int userId = getChangingUserId();
final S service = peekServiceForUserLocked(userId);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d49270d..097480b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1974,15 +1974,6 @@
+ " is already the parent of r=" + this);
}
- // TODO: Ensure that we do not directly reparent activities across stacks, as that may leave
- // the stacks in strange states. For now, we should use Task.reparent() to ensure that
- // the stack is left in an OK state.
- if (prevTask != null && newTask != null && prevTask.getStack() != newTask.getStack()) {
- throw new IllegalArgumentException(reason + ": task=" + newTask
- + " is in a different stack (" + newTask.getStackId() + ") than the parent of"
- + " r=" + this + " (" + prevTask.getStackId() + ")");
- }
-
ProtoLog.i(WM_DEBUG_ADD_REMOVE, "reparent: moving activity=%s"
+ " to task=%d at %d", this, task.mTaskId, position);
reparent(newTask, position);
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index bf6a81e..054541c 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -2679,7 +2679,7 @@
final Task task = taskTop.getTask();
// If ActivityOptions are moved out and need to be aborted or moved to taskTop.
- final ActivityOptions topOptions = sResetTargetTaskHelper.process(this, task, forceReset);
+ final ActivityOptions topOptions = sResetTargetTaskHelper.process(task, forceReset);
if (mChildren.contains(task)) {
final ActivityRecord newTop = task.getTopNonFinishingActivity();
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index 53d7688..a5c90a1 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -37,8 +37,8 @@
/** Helper class for processing the reset of a task. */
class ResetTargetTaskHelper {
private Task mTask;
- private ActivityStack mParent;
private Task mTargetTask;
+ private ActivityStack mTargetStack;
private ActivityRecord mRoot;
private boolean mForceReset;
private boolean mCanMoveOptions;
@@ -47,7 +47,7 @@
private ActivityOptions mTopOptions;
private ArrayList<ActivityRecord> mResultActivities = new ArrayList<>();
private ArrayList<ActivityRecord> mAllActivities = new ArrayList<>();
- private ArrayList<Task> mCreatedTasks = new ArrayList<>();
+ private ArrayList<ActivityRecord> mPendingReparentActivities = new ArrayList<>();
private void reset(Task task) {
mTask = task;
@@ -56,23 +56,22 @@
mTopOptions = null;
mResultActivities.clear();
mAllActivities.clear();
- mCreatedTasks.clear();
}
- ActivityOptions process(ActivityStack parent, Task targetTask, boolean forceReset) {
- mParent = parent;
+ ActivityOptions process(Task targetTask, boolean forceReset) {
mForceReset = forceReset;
mTargetTask = targetTask;
mTargetTaskFound = false;
+ mTargetStack = targetTask.getStack();
mActivityReparentPosition = -1;
final PooledConsumer c = PooledLambda.obtainConsumer(
ResetTargetTaskHelper::processTask, this, PooledLambda.__(Task.class));
- parent.forAllTasks(c);
+ targetTask.mWmService.mRoot.forAllTasks(c);
c.recycle();
+ processPendingReparentActivities();
reset(null);
- mParent = null;
return mTopOptions;
}
@@ -89,14 +88,9 @@
PooledLambda.__(ActivityRecord.class), isTargetTask);
task.forAllActivities(f);
f.recycle();
-
- processCreatedTasks();
}
private boolean processActivity(ActivityRecord r, boolean isTargetTask) {
- // End processing if we have reached the root.
- if (r == mRoot) return true;
-
mAllActivities.add(r);
final int flags = r.info.flags;
final boolean finishOnTaskLaunch =
@@ -122,32 +116,9 @@
// it out of here. We will move it as far out of the way as possible, to the
// bottom of the activity stack. This also keeps it correctly ordered with
// any activities we previously moved.
- // TODO: We should probably look for other stacks also, since corresponding
- // task with the same affinity is unlikely to be in the same stack.
- final Task targetTask;
- final ActivityRecord bottom = mParent.getBottomMostActivity();
- if (bottom != null && r.taskAffinity.equals(bottom.getTask().affinity)) {
- // If the activity currently at the bottom has the same task affinity as
- // the one we are moving, then merge it into the same task.
- targetTask = bottom.getTask();
- if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity "
- + r + " out to bottom task " + targetTask);
- } else {
- targetTask = mParent.createTask(
- mParent.mStackSupervisor.getNextTaskIdForUser(r.mUserId),
- r.info, null /* intent */, null /* voiceSession */,
- null /* voiceInteractor */, false /* toTop */);
- targetTask.affinityIntent = r.intent;
- mCreatedTasks.add(targetTask);
- if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity "
- + r + " out to new task " + targetTask);
- }
-
- mResultActivities.add(r);
- processResultActivities(r, targetTask, 0 /*bottom*/, true, true);
- mParent.positionChildAtBottom(targetTask);
- mParent.mStackSupervisor.mRecentTasks.add(targetTask);
+ // Handle this activity after we have done traversing the hierarchy.
+ mPendingReparentActivities.add(r);
return false;
}
}
@@ -203,8 +174,6 @@
processResultActivities(
r, mTargetTask, mActivityReparentPosition, false, false);
- mParent.positionChildAtTop(mTargetTask);
-
// Now we've moved it in to place...but what if this is a singleTop activity and
// we have put it on top of another instance of the same activity? Then we drop
// the instance below so it remains singleTop.
@@ -255,23 +224,51 @@
}
}
- private void processCreatedTasks() {
- if (mCreatedTasks.isEmpty()) return;
-
- DisplayContent display = mParent.getDisplay();
- final boolean singleTaskInstanceDisplay = display.isSingleTaskInstance();
- if (singleTaskInstanceDisplay) {
- display = mParent.mRootWindowContainer.getDefaultDisplay();
+ private void processPendingReparentActivities() {
+ if (mPendingReparentActivities.isEmpty()) {
+ return;
}
- final int windowingMode = mParent.getWindowingMode();
- final int activityType = mParent.getActivityType();
+ final ActivityTaskManagerService atmService = mTargetStack.mAtmService;
+ final ArrayList<Task> createdTasks = new ArrayList<>();
+ while (!mPendingReparentActivities.isEmpty()) {
+ final ActivityRecord r = mPendingReparentActivities.remove(0);
+ final ActivityRecord bottom = mTargetStack.getBottomMostActivity();
+ final Task targetTask;
+ if (bottom != null && r.taskAffinity.equals(bottom.getTask().affinity)) {
+ // If the activity currently at the bottom has the same task affinity as
+ // the one we are moving, then merge it into the same task.
+ targetTask = bottom.getTask();
+ if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity "
+ + r + " out to bottom task " + targetTask);
+ } else {
+ targetTask = mTargetStack.createTask(
+ atmService.mStackSupervisor.getNextTaskIdForUser(r.mUserId), r.info,
+ null /* intent */, null /* voiceSession */, null /* voiceInteractor */,
+ false /* toTop */);
+ targetTask.affinityIntent = r.intent;
+ createdTasks.add(targetTask);
+ if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity "
+ + r + " out to new task " + targetTask);
+ }
+ r.reparent(targetTask, 0 /* position */, "resetTargetTaskIfNeeded");
+ atmService.mStackSupervisor.mRecentTasks.add(targetTask);
+ }
+
+ DisplayContent display = mTargetStack.getDisplay();
+ final boolean singleTaskInstanceDisplay = display.isSingleTaskInstance();
+ if (singleTaskInstanceDisplay) {
+ display = atmService.mRootWindowContainer.getDefaultDisplay();
+ }
+
+ final int windowingMode = mTargetStack.getWindowingMode();
+ final int activityType = mTargetStack.getActivityType();
if (!singleTaskInstanceDisplay && !display.alwaysCreateStack(windowingMode, activityType)) {
return;
}
- while (!mCreatedTasks.isEmpty()) {
- final Task targetTask = mCreatedTasks.remove(mCreatedTasks.size() - 1);
+ while (!createdTasks.isEmpty()) {
+ final Task targetTask = createdTasks.remove(createdTasks.size() - 1);
final ActivityStack targetStack = display.getOrCreateStack(
windowingMode, activityType, false /* onTop */);
targetTask.reparent(targetStack, false /* toTop */, REPARENT_LEAVE_STACK_IN_PLACE,