diff options
35 files changed, 872 insertions, 172 deletions
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index a4688d128704..f56a6ad855d4 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -274,7 +274,7 @@ public class ResourcesManager { if (libDir.endsWith(".apk")) { // Avoid opening files we know do not have resources, // like code-only .jar files. - if (assets.addAssetPath(libDir) == 0) { + if (assets.addAssetPathAsSharedLibrary(libDir) == 0) { Log.w(TAG, "Asset path '" + libDir + "' does not exist or contains no resources."); } @@ -330,6 +330,22 @@ public class ResourcesManager { } /** + * Finds a cached ResourcesImpl object that matches the given ResourcesKey, or + * creates a new one and caches it for future use. + * @param key The key to match. + * @return a ResourcesImpl object matching the key. + */ + private @NonNull ResourcesImpl findOrCreateResourcesImplForKeyLocked( + @NonNull ResourcesKey key) { + ResourcesImpl impl = findResourcesImplForKeyLocked(key); + if (impl == null) { + impl = createResourcesImpl(key); + mResourceImpls.put(key, new WeakReference<>(impl)); + } + return impl; + } + + /** * Find the ResourcesKey that this ResourcesImpl object is associated with. * @return the ResourcesKey or null if none was found. */ @@ -811,4 +827,75 @@ public class ResourcesManager { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); } } + + /** + * Appends the library asset path to any ResourcesImpl object that contains the main + * assetPath. + * @param assetPath The main asset path for which to add the library asset path. + * @param libAsset The library asset path to add. + */ + public void appendLibAssetForMainAssetPath(String assetPath, String libAsset) { + synchronized (this) { + // Record which ResourcesImpl need updating + // (and what ResourcesKey they should update to). + final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>(); + + final int implCount = mResourceImpls.size(); + for (int i = 0; i < implCount; i++) { + final ResourcesImpl impl = mResourceImpls.valueAt(i).get(); + final ResourcesKey key = mResourceImpls.keyAt(i); + if (impl != null && key.mResDir.equals(assetPath)) { + if (!ArrayUtils.contains(key.mLibDirs, libAsset)) { + final int newLibAssetCount = 1 + + (key.mLibDirs != null ? key.mLibDirs.length : 0); + final String[] newLibAssets = new String[newLibAssetCount]; + if (key.mLibDirs != null) { + System.arraycopy(key.mLibDirs, 0, newLibAssets, 0, key.mLibDirs.length); + } + newLibAssets[newLibAssetCount - 1] = libAsset; + + updatedResourceKeys.put(impl, new ResourcesKey( + key.mResDir, + key.mSplitResDirs, + key.mOverlayDirs, + newLibAssets, + key.mDisplayId, + key.mOverrideConfiguration, + key.mCompatInfo)); + } + } + } + + // Bail early if there is no work to do. + if (updatedResourceKeys.isEmpty()) { + return; + } + + // Update any references to ResourcesImpl that require reloading. + final int resourcesCount = mResourceReferences.size(); + for (int i = 0; i < resourcesCount; i++) { + final Resources r = mResourceReferences.get(i).get(); + if (r != null) { + final ResourcesKey key = updatedResourceKeys.get(r.getImpl()); + if (key != null) { + r.setImpl(findOrCreateResourcesImplForKeyLocked(key)); + } + } + } + + // Update any references to ResourcesImpl that require reloading for each Activity. + for (ActivityResources activityResources : mActivityResourceReferences.values()) { + final int resCount = activityResources.activityResources.size(); + for (int i = 0; i < resCount; i++) { + final Resources r = activityResources.activityResources.get(i).get(); + if (r != null) { + final ResourcesKey key = updatedResourceKeys.get(r.getImpl()); + if (key != null) { + r.setImpl(findOrCreateResourcesImplForKeyLocked(key)); + } + } + } + } + } + } } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 108350a2b9ce..0881c9cb2208 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -3646,6 +3646,12 @@ public abstract class Context { public static final String SYSTEM_HEALTH_SERVICE = "systemhealth"; /** + * Gatekeeper Service. + * @hide + */ + public static final String GATEKEEPER_SERVICE = "android.service.gatekeeper.IGateKeeperService"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/hardware/location/ContextHubService.java b/core/java/android/hardware/location/ContextHubService.java index 8176189dffa0..43e596fe5566 100644 --- a/core/java/android/hardware/location/ContextHubService.java +++ b/core/java/android/hardware/location/ContextHubService.java @@ -21,12 +21,15 @@ import android.content.Context; import android.content.pm.PackageManager; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.ServiceManager; +import android.service.vr.IVrManager; +import android.service.vr.IVrStateCallbacks; import android.util.Log; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; /** * @hide @@ -57,8 +60,11 @@ public class ContextHubService extends IContextHubService.Stub { private static final int OS_APP_INSTANCE = -1; + private static final long APP_ID_ACTIVITY_RECOGNITION = 0x476f6f676c001000L; + private final Context mContext; - private final HashMap<Integer, NanoAppInstanceInfo> mNanoAppHash = new HashMap<>(); + private final ConcurrentHashMap<Integer, NanoAppInstanceInfo> mNanoAppHash = + new ConcurrentHashMap<>(); private final ContextHubInfo[] mContextHubInfo; private final RemoteCallbackList<IContextHubCallback> mCallbacksList = new RemoteCallbackList<>(); @@ -66,6 +72,18 @@ public class ContextHubService extends IContextHubService.Stub { private native int nativeSendMessage(int[] header, byte[] data); private native ContextHubInfo[] nativeInitialize(); + private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() { + @Override + public void onVrStateChanged(boolean enabled) { + for (NanoAppInstanceInfo app : mNanoAppHash.values()) { + if (app.getAppId() == APP_ID_ACTIVITY_RECOGNITION) { + sendVrStateChangeMessageToApp(app, enabled); + break; + } + } + } + }; + public ContextHubService(Context context) { mContext = context; mContextHubInfo = nativeInitialize(); @@ -74,6 +92,18 @@ public class ContextHubService extends IContextHubService.Stub { Log.d(TAG, "ContextHub[" + i + "] id: " + mContextHubInfo[i].getId() + ", name: " + mContextHubInfo[i].getName()); } + + if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) { + IVrManager vrManager = + IVrManager.Stub.asInterface(ServiceManager.getService("vrmanager")); + if (vrManager != null) { + try { + vrManager.registerListener(mVrStateCallbacks); + } catch (RemoteException e) { + Log.e(TAG, "VR state listener registration failed", e); + } + } + } } @Override @@ -277,4 +307,19 @@ public class ContextHubService extends IContextHubService.Stub { return 0; } + + private void sendVrStateChangeMessageToApp(NanoAppInstanceInfo app, boolean vrModeEnabled) { + int[] msgHeader = new int[MSG_HEADER_SIZE]; + msgHeader[MSG_FIELD_TYPE] = 0; + msgHeader[MSG_FIELD_VERSION] = 0; + msgHeader[MSG_FIELD_HUB_HANDLE] = ANY_HUB; + msgHeader[MSG_FIELD_APP_INSTANCE] = app.getHandle(); + + byte[] data = new byte[1]; + data[0] = (byte) ((vrModeEnabled) ? 1 : 0); + int ret = nativeSendMessage(msgHeader, data); + if (ret != 0) { + Log.e(TAG, "Couldn't send VR state change notification (" + ret + ")!"); + } + } } diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index f6e8940db408..d8be2b6dcc4c 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -240,6 +240,22 @@ public class Environment { return new File(getDataDirectory(), "system"); } + /** + * Returns the base directory for per-user system directory, device encrypted. + * {@hide} + */ + public static File getDataSystemDeDirectory() { + return buildPath(getDataDirectory(), "system_de"); + } + + /** + * Returns the base directory for per-user system directory, credential encrypted. + * {@hide} + */ + public static File getDataSystemCeDirectory() { + return buildPath(getDataDirectory(), "system_ce"); + } + /** {@hide} */ public static File getDataSystemCeDirectory(int userId) { return buildPath(getDataDirectory(), "system_ce", String.valueOf(userId)); diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index 7ff01daa6892..dd7be53d9865 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -730,7 +730,7 @@ public class RecoverySystem { if (line.startsWith("time")) { timeTotal = scaled; - } else if (line.startsWith("source_version")) { + } else if (line.startsWith("source_build")) { sourceVersion = scaled; } else if (line.startsWith("bytes_written")) { bytesWrittenInMiB = (bytesWrittenInMiB == -1) ? scaled : diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 7146448ad5d7..bcc8d463e51d 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -1496,7 +1496,9 @@ public class UserManager { } /** - * Returns information for all users on this device. + * Returns information for all users on this device, including ones marked for deletion. + * To retrieve only users that are alive, use {@link #getUsers(boolean)}. + * <p> * Requires {@link android.Manifest.permission#MANAGE_USERS} permission. * @return the list of users that exist on the device. * @hide diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java index 3069e5ade582..c46acae38c2c 100644 --- a/core/java/android/view/NotificationHeaderView.java +++ b/core/java/android/view/NotificationHeaderView.java @@ -22,6 +22,7 @@ import android.graphics.Canvas; import android.graphics.Outline; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Bundle; import android.util.AttributeSet; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.ImageView; @@ -63,6 +64,33 @@ public class NotificationHeaderView extends ViewGroup { } } }; + final AccessibilityDelegate mExpandDelegate = new AccessibilityDelegate() { + + @Override + public boolean performAccessibilityAction(View host, int action, Bundle args) { + if (super.performAccessibilityAction(host, action, args)) { + return true; + } + if (action == AccessibilityNodeInfo.ACTION_COLLAPSE + || action == AccessibilityNodeInfo.ACTION_EXPAND) { + mExpandClickListener.onClick(mExpandButton); + return true; + } + return false; + } + + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + // Avoid that the button description is also spoken + info.setClassName(getClass().getName()); + if (mExpanded) { + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE); + } else { + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND); + } + } + }; public NotificationHeaderView(Context context) { this(context, null); @@ -92,6 +120,9 @@ public class NotificationHeaderView extends ViewGroup { mAppName = findViewById(com.android.internal.R.id.app_name_text); mHeaderText = findViewById(com.android.internal.R.id.header_text); mExpandButton = (ImageView) findViewById(com.android.internal.R.id.expand_button); + if (mExpandButton != null) { + mExpandButton.setAccessibilityDelegate(mExpandDelegate); + } mIcon = findViewById(com.android.internal.R.id.icon); mProfileBadge = findViewById(com.android.internal.R.id.profile_badge); } @@ -230,7 +261,7 @@ public class NotificationHeaderView extends ViewGroup { public void setOnClickListener(@Nullable OnClickListener l) { mExpandClickListener = l; setOnTouchListener(mExpandClickListener != null ? mTouchListener : null); - setFocusable(l != null); + mExpandButton.setOnClickListener(mExpandClickListener); updateTouchListener(); } @@ -380,19 +411,6 @@ public class NotificationHeaderView extends ViewGroup { return this; } - @Override - public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(info); - if (mExpandClickListener != null) { - AccessibilityNodeInfo.AccessibilityAction expand - = new AccessibilityNodeInfo.AccessibilityAction( - AccessibilityNodeInfo.ACTION_CLICK, - getContext().getString( - com.android.internal.R.string.expand_action_accessibility)); - info.addAction(expand); - } - } - public ImageView getExpandButton() { return mExpandButton; } diff --git a/core/java/android/webkit/WebViewDelegate.java b/core/java/android/webkit/WebViewDelegate.java index 9e73af273c35..2cdff7906462 100644 --- a/core/java/android/webkit/WebViewDelegate.java +++ b/core/java/android/webkit/WebViewDelegate.java @@ -21,7 +21,9 @@ import android.annotation.Nullable; import android.annotation.SystemApi; import android.app.ActivityThread; import android.app.Application; +import android.app.ResourcesManager; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.graphics.Canvas; import android.os.SystemProperties; @@ -31,6 +33,8 @@ import android.view.DisplayListCanvas; import android.view.View; import android.view.ViewRootImpl; +import com.android.internal.util.ArrayUtils; + /** * Delegate used by the WebView provider implementation to access * the required framework functionality needed to implement a {@link WebView}. @@ -177,7 +181,29 @@ public final class WebViewDelegate { * Adds the WebView asset path to {@link android.content.res.AssetManager}. */ public void addWebViewAssetPath(Context context) { - context.getAssets().addAssetPathAsSharedLibrary( - WebViewFactory.getLoadedPackageInfo().applicationInfo.sourceDir); + final String newAssetPath = WebViewFactory.getLoadedPackageInfo().applicationInfo.sourceDir; + + final ApplicationInfo appInfo = context.getApplicationInfo(); + final String[] libs = appInfo.sharedLibraryFiles; + if (!ArrayUtils.contains(libs, newAssetPath)) { + // Build the new library asset path list. + final int newLibAssetsCount = 1 + (libs != null ? libs.length : 0); + final String[] newLibAssets = new String[newLibAssetsCount]; + if (libs != null) { + System.arraycopy(libs, 0, newLibAssets, 0, libs.length); + } + newLibAssets[newLibAssetsCount - 1] = newAssetPath; + + // Update the ApplicationInfo object with the new list. + // We know this will persist and future Resources created via ResourcesManager + // will include the shared library because this ApplicationInfo comes from the + // underlying LoadedApk in ContextImpl, which does not change during the life of the + // application. + appInfo.sharedLibraryFiles = newLibAssets; + + // Update existing Resources with the WebView library. + ResourcesManager.getInstance().appendLibAssetForMainAssetPath( + appInfo.getBaseResourcePath(), newAssetPath); + } } } diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java index 04929a76859e..d0719eeca04e 100644 --- a/core/java/com/android/internal/app/LocalePickerWithRegion.java +++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java @@ -45,6 +45,7 @@ import java.util.Set; * default locale.</p> */ public class LocalePickerWithRegion extends ListFragment implements SearchView.OnQueryTextListener { + private static final String PARENT_FRAGMENT_NAME = "localeListEditor"; private SuggestedLocaleAdapter mAdapter; private LocaleSelectedListener mListener; @@ -130,11 +131,24 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O return true; } + private void returnToParentFrame() { + getFragmentManager().popBackStack(PARENT_FRAGMENT_NAME, + FragmentManager.POP_BACK_STACK_INCLUSIVE); + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); + if (mLocaleList == null) { + // The fragment was killed and restored by the FragmentManager. + // At this point we have no data, no listener. Just return, to prevend a NPE. + // Fixes b/28748150. Created b/29400003 for a cleaner solution. + returnToParentFrame(); + return; + } + final boolean countryMode = mParentLocale != null; final Locale sortingLocale = countryMode ? mParentLocale.getLocale() : Locale.getDefault(); mAdapter = new SuggestedLocaleAdapter(mLocaleList, countryMode); @@ -197,8 +211,7 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O if (mListener != null) { mListener.onLocaleSelected(locale); } - getFragmentManager().popBackStack("localeListEditor", - FragmentManager.POP_BACK_STACK_INCLUSIVE); + returnToParentFrame(); } else { LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker( getContext(), mListener, locale, mTranslatedOnly /* translate only */); @@ -208,8 +221,7 @@ public class LocalePickerWithRegion extends ListFragment implements SearchView.O .replace(getId(), selector).addToBackStack(null) .commit(); } else { - getFragmentManager().popBackStack("localeListEditor", - FragmentManager.POP_BACK_STACK_INCLUSIVE); + returnToParentFrame(); } } } diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java new file mode 100644 index 000000000000..f4f49b1e4ffe --- /dev/null +++ b/core/java/com/android/internal/widget/NotificationExpandButton.java @@ -0,0 +1,62 @@ +/* + * 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.internal.widget; + +import android.annotation.Nullable; +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.widget.ImageView; +import android.widget.RemoteViews; + +/** + * An expand button in a notification + */ +@RemoteViews.RemoteView +public class NotificationExpandButton extends ImageView { + public NotificationExpandButton(Context context) { + super(context); + } + + public NotificationExpandButton(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public NotificationExpandButton(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public NotificationExpandButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public void getBoundsOnScreen(Rect outRect, boolean clipToParent) { + super.getBoundsOnScreen(outRect, clipToParent); + extendRectToMinTouchSize(outRect); + } + + private void extendRectToMinTouchSize(Rect rect) { + int touchTargetSize = (int) (getResources().getDisplayMetrics().density * 48); + rect.left = rect.centerX() - touchTargetSize / 2; + rect.right = rect.left + touchTargetSize; + rect.top = rect.centerY() - touchTargetSize / 2; + rect.bottom = rect.top + touchTargetSize; + } +} diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 38ea92a73a5a..38f671c21cbb 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -89,7 +89,7 @@ android:layout="@layout/notification_template_part_chronometer" android:visibility="gone" /> - <ImageView + <com.android.internal.widget.NotificationExpandButton android:id="@+id/expand_button" android:background="@null" android:layout_width="wrap_content" diff --git a/docs/html/preview/behavior-changes.jd b/docs/html/preview/behavior-changes.jd index b38f1b8d6dbe..3a3729534462 100644 --- a/docs/html/preview/behavior-changes.jd +++ b/docs/html/preview/behavior-changes.jd @@ -737,7 +737,17 @@ JavaVM::AttachCurrentThread from <jni.h>. <li>The Work Mode setting controls access to work apps. When work mode is off the system launcher indicates work apps are unavailable by greying them out. Enabling - work mode again restores normal behavior. + work mode again restores normal behavior.</li> + + <li>When installing a PKCS #12 file containing a client certificate chain and + the corresponding private key from Settings UI, the CA certificate in the + chain is no longer installed to the trusted credentials storage. This does + not affect the result of {@link android.security.KeyChain#getCertificateChain + KeyChain.getCertificateChain()} when apps attempt to retrieve the client + certificate chain later. If required, the CA certificate should be installed + to the trusted credentials storage via Settings UI separately, with a + DER-encoded format under a .crt or .cer file extension. + </li> </ul> <p> diff --git a/docs/html/preview/support.jd b/docs/html/preview/support.jd index ba30fe3455af..5328fd8b5eb5 100644 --- a/docs/html/preview/support.jd +++ b/docs/html/preview/support.jd @@ -257,6 +257,26 @@ page.image=images/cards/card-n-support_2x.png server when in a Work profile. This issue will be resolved in the next Developer Preview. </li> + + <li>After reboot with work mode off, solving work challenge does not switch + on work mode. + </li> + + <li>Users receiving a video call in Hangouts have to unlock work challenge + first. + </li> + + <li>Accessing Settings > Security > Device Security crash observed when + separating primary and work challenge. + </li> + + <li>If {@link android.os.UserManager#DISALLOW_CONFIG_VPN} is set before + calling {@link android.app.admin.DevicePolicyManager#setAlwaysOnVpnPackage + DevicePolicyManager.setAlwaysOnVpnPackage()}, then setting always on VPN does + not work. That is, after rebooting the device with the {@link + android.os.UserManager#DISALLOW_CONFIG_VPN} restriction set, VPN is not + autostarted. + </li> </ul> <!-- TBA, if any diff --git a/docs/html/training/wearables/watch-faces/service.jd b/docs/html/training/wearables/watch-faces/service.jd index 20eb0c777e0c..b54f51a9cc10 100755 --- a/docs/html/training/wearables/watch-faces/service.jd +++ b/docs/html/training/wearables/watch-faces/service.jd @@ -93,24 +93,20 @@ it provides project setup, library inclusion, and packaging conveniences.</p> <h3 id="Permissions">Declare Permissions</h3> -<p>Watch faces require the <code>PROVIDE_BACKGROUND</code> and <code>WAKE_LOCK</code> permissions. -Add the following permissions to the manifest files of both the wearable app and the mobile -app under the <code>manifest</code> element:</p> +<p>A watch face requires the <code>WAKE_LOCK</code> permission. +Add the following permission to the manifest files of both the wearable app +and the mobile app under the <code>manifest</code> element:</p> <pre> <manifest ...> <uses-permission - android:name="com.google.android.permission.PROVIDE_BACKGROUND" /> - <uses-permission android:name="android.permission.WAKE_LOCK" /> ... </manifest> </pre> -<p class="caution"><strong>Caution:</strong> The handheld app must include all the permissions -declared in the wearable app.</p> - - +<p class="caution"><strong>Caution:</strong> The handheld app must include all +of the permissions declared in the wearable app.</p> <h2 id="CallbackMethods">Implement the Service and Callback Methods</h2> diff --git a/keystore/java/android/security/GateKeeper.java b/keystore/java/android/security/GateKeeper.java index c1df28c387e5..7a2cbd06eb92 100644 --- a/keystore/java/android/security/GateKeeper.java +++ b/keystore/java/android/security/GateKeeper.java @@ -16,6 +16,7 @@ package android.security; +import android.content.Context; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -32,7 +33,7 @@ public abstract class GateKeeper { public static IGateKeeperService getService() { IGateKeeperService service = IGateKeeperService.Stub.asInterface( - ServiceManager.getService("android.service.gatekeeper.IGateKeeperService")); + ServiceManager.getService(Context.GATEKEEPER_SERVICE)); if (service == null) { throw new IllegalStateException("Gatekeeper service not available"); } diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index d447a38c9b90..ceeb12bab205 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -6174,7 +6174,7 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, if (id >= 256) { LOG_ALWAYS_FATAL("Package id out of range"); return NO_ERROR; - } else if (id == 0 || appAsLib || isSystemAsset) { + } else if (id == 0 || (id == 0x7f && appAsLib) || isSystemAsset) { // This is a library or a system asset, so assign an ID id = mNextPackageId++; } diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java index ce916cba312a..37e3c531229a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java @@ -308,6 +308,13 @@ public class SettingsDrawerActivity extends Activity { } @Override + protected void onPreExecute() { + if (sConfigTracker == null || sTileCache == null) { + getDashboardCategories(); + } + } + + @Override protected void onPostExecute(List<DashboardCategory> dashboardCategories) { for (int i = 0; i < dashboardCategories.size(); i++) { DashboardCategory category = dashboardCategories.get(i); diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java index 913da185c40d..3921a20cffd1 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsSystemUser.java @@ -30,6 +30,7 @@ import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.DockedTopTaskEvent; import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent; import com.android.systemui.recents.events.ui.RecentsDrawnEvent; +import com.android.systemui.recents.misc.ForegroundThread; /** * An implementation of the system user's Recents interface to be called remotely by secondary @@ -78,12 +79,16 @@ public class RecentsSystemUser extends IRecentsSystemUserCallbacks.Stub { @Override public void updateRecentsVisibility(boolean visible) { - mImpl.onVisibilityChanged(mContext, visible); + ForegroundThread.getHandler().post(() -> { + mImpl.onVisibilityChanged(mContext, visible); + }); } @Override public void startScreenPinning(int taskId) { - mImpl.onStartScreenPinning(mContext, taskId); + ForegroundThread.getHandler().post(() -> { + mImpl.onStartScreenPinning(mContext, taskId); + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java index 992b13fbfc24..b961055c6fed 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java +++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java @@ -72,7 +72,12 @@ public class ScreenPinningRequest implements View.OnClickListener { } public void showPrompt(int taskId, boolean allowCancel) { - clearPrompt(); + try { + clearPrompt(); + } catch (IllegalArgumentException e) { + // If the call to show the prompt fails due to the request window not already being + // attached, then just ignore the error since we will be re-adding it below. + } this.taskId = taskId; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 28a6851307fc..e1d4c8aea83c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -27,6 +27,7 @@ import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; +import android.os.Bundle; import android.service.notification.StatusBarNotification; import android.util.AttributeSet; import android.util.FloatProperty; @@ -37,11 +38,11 @@ import android.view.NotificationHeaderView; import android.view.View; import android.view.ViewStub; import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.Chronometer; import android.widget.ImageView; import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.MetricsProto; import com.android.internal.logging.MetricsProto.MetricsEvent; import com.android.internal.util.NotificationColorUtil; import com.android.systemui.R; @@ -50,6 +51,7 @@ import com.android.systemui.statusbar.notification.HybridNotificationView; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.stack.NotificationChildrenContainer; +import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.StackScrollState; import com.android.systemui.statusbar.stack.StackStateAnimator; import com.android.systemui.statusbar.stack.StackViewState; @@ -140,15 +142,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { @Override public void onClick(View v) { if (!mShowingPublic && mGroupManager.isSummaryOfGroup(mStatusBarNotification)) { + mGroupExpansionChanging = true; final boolean wasExpanded = mGroupManager.isGroupExpanded(mStatusBarNotification); boolean nowExpanded = mGroupManager.toggleGroupExpansion(mStatusBarNotification); mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded); - mGroupExpansionChanging = true; - updateBackgroundForGroupState(); MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTIFICATION_GROUP_EXPANDER, nowExpanded); logExpansionEvent(true /* userAction */, wasExpanded); } else { + if (v.isAccessibilityFocused()) { + mPrivateLayout.setFocusOnVisibilityChange(); + } boolean nowExpanded; if (isPinned()) { nowExpanded = !mExpandedWhenPinned; @@ -181,6 +185,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } }; private OnClickListener mOnClickListener; + private View mChildAfterViewWhenDismissed; + private View mGroupParentWhenDismissed; + private boolean mRefocusOnDismiss; public boolean isGroupExpansionChanging() { if (isChildInGroup()) { @@ -717,8 +724,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } } - public void setDismissed(boolean dismissed) { + public void setDismissed(boolean dismissed, boolean fromAccessibility) { mDismissed = dismissed; + mGroupParentWhenDismissed = mNotificationParent; + mRefocusOnDismiss = fromAccessibility; + mChildAfterViewWhenDismissed = null; + if (isChildInGroup()) { + List<ExpandableNotificationRow> notificationChildren = + mNotificationParent.getNotificationChildren(); + int i = notificationChildren.indexOf(this); + if (i != -1 && i < notificationChildren.size() - 1) { + mChildAfterViewWhenDismissed = notificationChildren.get(i + 1); + } + } } public boolean isDismissed() { @@ -750,6 +768,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { return mChildrenContainer; } + public View getChildAfterViewWhenDismissed() { + return mChildAfterViewWhenDismissed; + } + + public View getGroupParentWhenDismissed() { + return mGroupParentWhenDismissed; + } + public interface ExpansionLogger { public void logNotificationExpansion(String key, boolean userAction, boolean expanded); } @@ -1326,8 +1352,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private void updateClearability() { // public versions cannot be dismissed - mVetoButton.setVisibility(isClearable() && (!mShowingPublic - || !mSensitiveHiddenInGeneral) ? View.VISIBLE : View.GONE); + mVetoButton.setVisibility(canViewBeDismissed() ? View.VISIBLE : View.GONE); + } + + private boolean canViewBeDismissed() { + return isClearable() && (!mShowingPublic || !mSensitiveHiddenInGeneral); } public void makeActionsVisibile() { @@ -1343,6 +1372,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { if (mChildrenContainer != null) { mChildrenContainer.setChildrenExpanded(expanded); } + updateBackgroundForGroupState(); updateClickAndFocus(); } @@ -1568,6 +1598,32 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } } + @Override + public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfoInternal(info); + if (canViewBeDismissed()) { + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_DISMISS); + } + } + + @Override + public boolean performAccessibilityActionInternal(int action, Bundle arguments) { + if (super.performAccessibilityActionInternal(action, arguments)) { + return true; + } + switch (action) { + case AccessibilityNodeInfo.ACTION_DISMISS: + NotificationStackScrollLayout.performDismiss(this, mGroupManager, + true /* fromAccessibility */); + return true; + } + return false; + } + + public boolean shouldRefocusOnDismiss() { + return mRefocusOnDismiss || isAccessibilityFocused(); + } + public interface OnExpandClickListener { void onExpandClicked(NotificationData.Entry clickedEntry, boolean nowExpanded); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index a11263a3b9f2..30ac9cad346f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -29,6 +29,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.widget.FrameLayout; +import android.widget.ImageView; import com.android.internal.util.NotificationColorUtil; import com.android.systemui.R; @@ -117,6 +118,7 @@ public class NotificationContentView extends FrameLayout { private PendingIntent mPreviousHeadsUpRemoteInputIntent; private int mContentHeightAtAnimationStart = UNDEFINED; + private boolean mFocusOnVisibilityChange; public NotificationContentView(Context context, AttributeSet attrs) { @@ -395,6 +397,19 @@ public class NotificationContentView extends FrameLayout { } } + private void focusExpandButtonIfNecessary() { + if (mFocusOnVisibilityChange) { + NotificationHeaderView header = getVisibleNotificationHeader(); + if (header != null) { + ImageView expandButton = header.getExpandButton(); + if (expandButton != null) { + expandButton.requestAccessibilityFocus(); + } + } + mFocusOnVisibilityChange = false; + } + } + public void setContentHeight(int contentHeight) { mContentHeight = Math.max(Math.min(contentHeight, getHeight()), getMinHeight()); selectLayout(mAnimate /* animate */, false /* force */); @@ -584,7 +599,8 @@ public class NotificationContentView extends FrameLayout { updateContentTransformation(); } else { int visibleType = calculateVisibleType(); - if (visibleType != mVisibleType || force) { + boolean changedType = visibleType != mVisibleType; + if (changedType || force) { View visibleView = getViewForVisibleType(visibleType); if (visibleView != null) { visibleView.setVisibility(VISIBLE); @@ -604,6 +620,9 @@ public class NotificationContentView extends FrameLayout { updateViewVisibilities(visibleType); } mVisibleType = visibleType; + if (changedType) { + focusExpandButtonIfNecessary(); + } updateBackgroundColor(animate); } } @@ -1133,4 +1152,8 @@ public class NotificationContentView extends FrameLayout { mContentHeightAtAnimationStart = UNDEFINED; } } + + public void setFocusOnVisibilityChange() { + mFocusOnVisibilityChange = true; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 7c391fbde0d6..43f847c23f81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -769,7 +769,7 @@ public class NotificationStackScrollLayout extends ViewGroup mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey()); } } - performDismiss(v); + performDismiss(v, mGroupManager, false /* fromAccessibility */); mFalsingManager.onNotificationDismissed(); if (mFalsingManager.shouldEnforceBouncer()) { @@ -778,17 +778,18 @@ public class NotificationStackScrollLayout extends ViewGroup } } - private void performDismiss(View v) { + public static void performDismiss(View v, NotificationGroupManager groupManager, + boolean fromAccessibility) { if (v instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) v; - if (mGroupManager.isOnlyChildInSuppressedGroup(row.getStatusBarNotification())) { + if (groupManager.isOnlyChildInGroup(row.getStatusBarNotification())) { ExpandableNotificationRow groupSummary = - mGroupManager.getLogicalGroupSummary(row.getStatusBarNotification()); + groupManager.getLogicalGroupSummary(row.getStatusBarNotification()); if (groupSummary.isClearable()) { - performDismiss(groupSummary); + performDismiss(groupSummary, groupManager, fromAccessibility); } } - row.setDismissed(true); + row.setDismissed(true, fromAccessibility); } final View veto = v.findViewById(R.id.veto); if (veto != null && veto.getVisibility() != View.GONE) { @@ -2265,6 +2266,27 @@ public class NotificationStackScrollLayout extends ViewGroup // Make sure the clipRect we might have set is removed expandableView.setClipTopAmount(0); + + focusNextViewIfFocused(child); + } + + private void focusNextViewIfFocused(View view) { + if (view instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) view; + if (row.shouldRefocusOnDismiss()) { + View nextView = row.getChildAfterViewWhenDismissed(); + if (nextView == null) { + View groupParentWhenDismissed = row.getGroupParentWhenDismissed(); + nextView = getFirstChildBelowTranlsationY(groupParentWhenDismissed != null + ? groupParentWhenDismissed.getTranslationY() + : view.getTranslationY()); + } + if (nextView != null) { + nextView.requestAccessibilityFocus(); + } + } + } + } private boolean isChildInGroup(View child) { diff --git a/services/core/java/com/android/server/AnyMotionDetector.java b/services/core/java/com/android/server/AnyMotionDetector.java index e98b4aa6e267..a8ae914d0e9a 100644 --- a/services/core/java/com/android/server/AnyMotionDetector.java +++ b/services/core/java/com/android/server/AnyMotionDetector.java @@ -308,7 +308,7 @@ public class AnyMotionDetector { /** * A timestamped three dimensional vector and some vector operations. */ - private static class Vector3 { + public static final class Vector3 { public long timeMillisSinceBoot; public float x; public float y; @@ -321,11 +321,11 @@ public class AnyMotionDetector { this.z = z; } - private float norm() { + public float norm() { return (float) Math.sqrt(dotProduct(this)); } - private Vector3 normalized() { + public Vector3 normalized() { float mag = norm(); return new Vector3(timeMillisSinceBoot, x / mag, y / mag, z / mag); } @@ -338,12 +338,20 @@ public class AnyMotionDetector { * @return angle between this vector and the other given one. */ public float angleBetween(Vector3 other) { - double degrees = Math.toDegrees(Math.acos(this.dotProduct(other))); - float returnValue = (float) degrees; + Vector3 crossVector = cross(other); + float degrees = Math.abs((float)Math.toDegrees( + Math.atan2(crossVector.norm(), dotProduct(other)))); Slog.d(TAG, "angleBetween: this = " + this.toString() + - ", other = " + other.toString()); - Slog.d(TAG, " degrees = " + degrees + ", returnValue = " + returnValue); - return returnValue; + ", other = " + other.toString() + ", degrees = " + degrees); + return degrees; + } + + public Vector3 cross(Vector3 v) { + return new Vector3( + v.timeMillisSinceBoot, + y * v.z - z * v.y, + z * v.x - x * v.z, + x * v.y - y * v.x); } @Override diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index eeb20bf03d12..1e715f93d041 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -1588,7 +1588,7 @@ public class LockSettingsService extends ILockSettings.Stub { } final IBinder service = - ServiceManager.getService("android.service.gatekeeper.IGateKeeperService"); + ServiceManager.getService(Context.GATEKEEPER_SERVICE); if (service != null) { service.linkToDeath(new GateKeeperDiedRecipient(), 0); mGateKeeperService = IGateKeeperService.Stub.asInterface(service); diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 408454286aee..0cf517274f30 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -3425,7 +3425,7 @@ public class AccountManagerService /** {@hide} */ @NonNull public AccountAndUser[] getAllAccounts() { - final List<UserInfo> users = getUserManager().getUsers(); + final List<UserInfo> users = getUserManager().getUsers(true); final int[] userIds = new int[users.size()]; for (int i = 0; i < userIds.length; i++) { userIds[i] = users.get(i).id; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 897aa203a548..09f24edd72ca 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2292,11 +2292,8 @@ public final class ActivityManagerService extends ActivityManagerNative final ActivityRecord r = (ActivityRecord) msg.obj; final boolean needsVrMode = r != null && r.requestedVrComponent != null; if (needsVrMode) { - VrManagerInternal vrService = - LocalServices.getService(VrManagerInternal.class); - boolean enable = msg.arg1 == 1; - vrService.setVrMode(enable, r.requestedVrComponent, r.userId, - r.info.getComponentName()); + applyVrMode(msg.arg1 == 1, r.requestedVrComponent, r.userId, + r.info.getComponentName(), false); } } break; } @@ -3084,6 +3081,17 @@ public final class ActivityManagerService extends ActivityManagerNative mHandler.obtainMessage(VR_MODE_APPLY_IF_NEEDED_MSG, enable ? 1 : 0, 0, r)); } + private void applyVrMode(boolean enabled, ComponentName packageName, int userId, + ComponentName callingPackage, boolean immediate) { + VrManagerInternal vrService = + LocalServices.getService(VrManagerInternal.class); + if (immediate) { + vrService.setVrModeImmediate(enabled, packageName, userId, callingPackage); + } else { + vrService.setVrMode(enabled, packageName, userId, callingPackage); + } + } + final void showAskCompatModeDialogLocked(ActivityRecord r) { Message msg = Message.obtain(); msg.what = SHOW_COMPAT_MODE_DIALOG_UI_MSG; @@ -21413,11 +21421,25 @@ public final class ActivityManagerService extends ActivityManagerNative public SleepToken acquireSleepToken(String tag) { Preconditions.checkNotNull(tag); + ComponentName requestedVrService = null; + ComponentName callingVrActivity = null; + int userId = -1; + synchronized (ActivityManagerService.this) { + if (mFocusedActivity != null) { + requestedVrService = mFocusedActivity.requestedVrComponent; + callingVrActivity = mFocusedActivity.info.getComponentName(); + userId = mFocusedActivity.userId; + } + } + + if (requestedVrService != null) { + applyVrMode(false, requestedVrService, userId, callingVrActivity, true); + } + synchronized (ActivityManagerService.this) { SleepTokenImpl token = new SleepTokenImpl(tag); mSleepTokens.add(token); updateSleepIfNeededLocked(); - applyVrModeIfNeededLocked(mFocusedActivity, false); return token; } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f2fde0f91e40..a560c83ef92e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -19352,10 +19352,18 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); if ((flags & StorageManager.FLAG_STORAGE_DE) != 0 && !mOnlyCore) { UserManagerService.enforceSerialNumber( Environment.getDataUserDeDirectory(volumeUuid, userId), userSerial); + if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) { + UserManagerService.enforceSerialNumber( + Environment.getDataSystemDeDirectory(userId), userSerial); + } } if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && !mOnlyCore) { UserManagerService.enforceSerialNumber( Environment.getDataUserCeDirectory(volumeUuid, userId), userSerial); + if (Objects.equals(volumeUuid, StorageManager.UUID_PRIVATE_INTERNAL)) { + UserManagerService.enforceSerialNumber( + Environment.getDataSystemCeDirectory(userId), userSerial); + } } synchronized (mInstallLock) { @@ -19424,6 +19432,10 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); .listFilesOrEmpty(Environment.getDataUserDeDirectory(volumeUuid))); Collections.addAll(files, FileUtils .listFilesOrEmpty(Environment.getDataUserCeDirectory(volumeUuid))); + Collections.addAll(files, FileUtils + .listFilesOrEmpty(Environment.getDataSystemDeDirectory())); + Collections.addAll(files, FileUtils + .listFilesOrEmpty(Environment.getDataSystemCeDirectory())); for (File file : files) { if (!file.isDirectory()) continue; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index a8cf110655f8..a85064b70f22 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -65,6 +65,8 @@ import android.os.UserManager; import android.os.UserManagerInternal; import android.os.UserManagerInternal.UserRestrictionsListener; import android.os.storage.StorageManager; +import android.security.GateKeeper; +import android.service.gatekeeper.IGateKeeperService; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; @@ -87,6 +89,7 @@ import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.LocalServices; +import com.android.server.SystemService; import com.android.server.am.UserState; import libcore.io.IoUtils; @@ -119,6 +122,7 @@ import java.util.List; * </ul> */ public class UserManagerService extends IUserManager.Stub { + private static final String LOG_TAG = "UserManagerService"; static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE private static final boolean DBG_WITH_STACKTRACE = false; // DO NOT SUBMIT WITH TRUE @@ -367,6 +371,31 @@ public class UserManagerService extends IUserManager.Stub { } } + public static class LifeCycle extends SystemService { + + private UserManagerService mUms; + + /** + * @param context + */ + public LifeCycle(Context context) { + super(context); + } + + @Override + public void onStart() { + mUms = UserManagerService.getInstance(); + publishBinderService(Context.USER_SERVICE, mUms); + } + + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { + mUms.cleanupPartialUsers(); + } + } + } + @VisibleForTesting UserManagerService(File dataDir) { this(null, null, new Object(), dataDir); @@ -408,25 +437,6 @@ public class UserManagerService extends IUserManager.Stub { } void systemReady() { - // Prune out any partially created, partially removed and ephemeral users. - ArrayList<UserInfo> partials = new ArrayList<>(); - synchronized (mUsersLock) { - final int userSize = mUsers.size(); - for (int i = 0; i < userSize; i++) { - UserInfo ui = mUsers.valueAt(i).info; - if ((ui.partial || ui.guestToRemove || ui.isEphemeral()) && i != 0) { - partials.add(ui); - } - } - } - final int partialsSize = partials.size(); - for (int i = 0; i < partialsSize; i++) { - UserInfo ui = partials.get(i); - Slog.w(LOG_TAG, "Removing partially created user " + ui.id - + " (name=" + ui.name + ")"); - removeUserState(ui.id); - } - mAppOpsService = IAppOpsService.Stub.asInterface( ServiceManager.getService(Context.APP_OPS_SERVICE)); @@ -447,6 +457,27 @@ public class UserManagerService extends IUserManager.Stub { null, mHandler); } + void cleanupPartialUsers() { + // Prune out any partially created, partially removed and ephemeral users. + ArrayList<UserInfo> partials = new ArrayList<>(); + synchronized (mUsersLock) { + final int userSize = mUsers.size(); + for (int i = 0; i < userSize; i++) { + UserInfo ui = mUsers.valueAt(i).info; + if ((ui.partial || ui.guestToRemove || ui.isEphemeral()) && i != 0) { + partials.add(ui); + } + } + } + final int partialsSize = partials.size(); + for (int i = 0; i < partialsSize; i++) { + UserInfo ui = partials.get(i); + Slog.w(LOG_TAG, "Removing partially created user " + ui.id + + " (name=" + ui.name + ")"); + removeUserState(ui.id); + } + } + @Override public String getUserAccount(int userId) { checkManageUserAndAcrossUsersFullPermission("get user account"); @@ -2472,8 +2503,23 @@ public class UserManagerService extends IUserManager.Stub { "Destroying key for user " + userHandle + " failed, continuing anyway", e); } + // Cleanup gatekeeper secure user id + try { + final IGateKeeperService gk = GateKeeper.getService(); + if (gk != null) { + gk.clearSecureUserId(userHandle); + } + } catch (Exception ex) { + Slog.w(LOG_TAG, "unable to clear GK secure user id"); + } + // Cleanup package manager settings mPm.cleanUpUser(this, userHandle); + + // Clean up all data before removing metadata + mPm.destroyUserData(userHandle, + StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE); + // Remove this user from the list synchronized (mUsersLock) { mUsers.remove(userHandle); @@ -2496,12 +2542,6 @@ public class UserManagerService extends IUserManager.Stub { AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX)); userFile.delete(); updateUserIds(); - - // Now that we've purged all the metadata above, destroy the actual data - // on disk; if we battery pull in here we'll finish cleaning up when - // reconciling after reboot. - mPm.destroyUserData(userHandle, - StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE); } private void sendProfileRemovedBroadcast(int parentUserId, int removedUserId) { diff --git a/services/core/java/com/android/server/vr/VrManagerInternal.java b/services/core/java/com/android/server/vr/VrManagerInternal.java index 1bbb9f52c818..ad87a885348e 100644 --- a/services/core/java/com/android/server/vr/VrManagerInternal.java +++ b/services/core/java/com/android/server/vr/VrManagerInternal.java @@ -43,6 +43,9 @@ public abstract class VrManagerInternal { /** * Set the current VR mode state. + * <p/> + * This may delay the mode change slightly during application transitions to avoid frequently + * tearing down VrListenerServices unless necessary. * * @param enabled {@code true} to enable VR mode. * @param packageName The package name of the requested VrListenerService to bind. @@ -52,6 +55,18 @@ public abstract class VrManagerInternal { public abstract void setVrMode(boolean enabled, @NonNull ComponentName packageName, int userId, @NonNull ComponentName calling); + /** + * Set the current VR mode state immediately. + * + * @param enabled {@code true} to enable VR mode. + * @param packageName The package name of the requested VrListenerService to bind. + * @param userId the user requesting the VrListenerService component. + * @param calling the component currently using VR mode, or null to leave unchanged. + */ + public abstract void setVrModeImmediate(boolean enabled, @NonNull ComponentName packageName, + int userId, @NonNull ComponentName calling); + + /** * Return NO_ERROR if the given package is installed on the device and enabled as a * VrListenerService for the given current user, or a negative error code indicating a failure. diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java index b4c4bd8daa76..5fefd4c8b2ce 100644 --- a/services/core/java/com/android/server/vr/VrManagerService.java +++ b/services/core/java/com/android/server/vr/VrManagerService.java @@ -218,7 +218,6 @@ public class VrManagerService extends SystemService implements EnabledComponentC String packageName = mNotificationAccessPackageToUserId.keyAt(i); revokeNotificationListenerAccess(packageName, grantUserId); revokeNotificationPolicyAccess(packageName); - revokeCoarseLocationPermissionIfNeeded(packageName, grantUserId); mNotificationAccessPackageToUserId.removeAt(i); } } @@ -227,7 +226,6 @@ public class VrManagerService extends SystemService implements EnabledComponentC if (!packageNames.contains(pkg)) { revokeNotificationListenerAccess(pkg, currentUserId); revokeNotificationPolicyAccess(pkg); - revokeCoarseLocationPermissionIfNeeded(pkg, currentUserId); mNotificationAccessPackageToUserId.remove(pkg); } } @@ -235,7 +233,6 @@ public class VrManagerService extends SystemService implements EnabledComponentC if (!allowed.contains(pkg)) { grantNotificationPolicyAccess(pkg); grantNotificationListenerAccess(pkg, currentUserId); - grantCoarseLocationPermissionIfNeeded(pkg, currentUserId); mNotificationAccessPackageToUserId.put(pkg, currentUserId); } } @@ -373,7 +370,13 @@ public class VrManagerService extends SystemService implements EnabledComponentC @Override public void setVrMode(boolean enabled, ComponentName packageName, int userId, ComponentName callingPackage) { - VrManagerService.this.setVrMode(enabled, packageName, userId, callingPackage); + VrManagerService.this.setVrMode(enabled, packageName, userId, callingPackage, false); + } + + @Override + public void setVrModeImmediate(boolean enabled, ComponentName packageName, int userId, + ComponentName callingPackage) { + VrManagerService.this.setVrMode(enabled, packageName, userId, callingPackage, true); } @Override @@ -754,22 +757,6 @@ public class VrManagerService extends SystemService implements EnabledComponentC flatSettings, userId); } - private void grantCoarseLocationPermissionIfNeeded(String pkg, int userId) { - // Don't clobber the user if permission set in current state explicitly - if (!isPermissionUserUpdated(Manifest.permission.ACCESS_COARSE_LOCATION, pkg, userId)) { - mContext.getPackageManager().grantRuntimePermission(pkg, - Manifest.permission.ACCESS_COARSE_LOCATION, new UserHandle(userId)); - } - } - - private void revokeCoarseLocationPermissionIfNeeded(String pkg, int userId) { - // Don't clobber the user if permission set in current state explicitly - if (!isPermissionUserUpdated(Manifest.permission.ACCESS_COARSE_LOCATION, pkg, userId)) { - mContext.getPackageManager().revokeRuntimePermission(pkg, - Manifest.permission.ACCESS_COARSE_LOCATION, new UserHandle(userId)); - } - } - private boolean isPermissionUserUpdated(String permission, String pkg, int userId) { final int flags = mContext.getPackageManager().getPermissionFlags( permission, pkg, new UserHandle(userId)); @@ -916,11 +903,11 @@ public class VrManagerService extends SystemService implements EnabledComponentC */ private void setVrMode(boolean enabled, @NonNull ComponentName targetPackageName, - int userId, @NonNull ComponentName callingPackage) { + int userId, @NonNull ComponentName callingPackage, boolean immediate) { synchronized (mLock) { - if (!enabled && mCurrentVrService != null) { + if (!enabled && mCurrentVrService != null && !immediate) { // If we're transitioning out of VR mode, delay briefly to avoid expensive HAL calls // and service bind/unbind in case we are immediately switching to another VR app. if (mPendingState == null) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java index 1ae1a773b23e..b53933e07f2b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java @@ -120,7 +120,7 @@ class Owners { // First, try to read from the legacy file. final File legacy = getLegacyConfigFileWithTestOverride(); - final List<UserInfo> users = mUserManager.getUsers(); + final List<UserInfo> users = mUserManager.getUsers(true); if (readLegacyOwnerFileLocked(legacy)) { if (DEBUG) { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 476a559eaea3..f59b2ff16482 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -470,7 +470,7 @@ public final class SystemServer { } traceBeginAndSlog("StartUserManagerService"); - ServiceManager.addService(Context.USER_SERVICE, UserManagerService.getInstance()); + mSystemServiceManager.startService(UserManagerService.LifeCycle.class); Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); // Initialize attribute cache used to cache resources from packages. diff --git a/services/tests/servicestests/src/com/android/server/Vector3Test.java b/services/tests/servicestests/src/com/android/server/Vector3Test.java new file mode 100644 index 000000000000..88dbe70b9940 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/Vector3Test.java @@ -0,0 +1,164 @@ +/* + * 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.server; + +import android.test.AndroidTestCase; + +import java.lang.Exception; +import java.lang.Math; + +/** + * Tests for {@link com.android.server.AnyMotionDetector.Vector3} + */ +public class Vector3Test extends AndroidTestCase { + private static final float tolerance = 1.0f / (1 << 12); + private static final float STATIONARY_ANGLE_THRESHOLD = 0.05f; + + private AnyMotionDetector.Vector3 unitXAxis; + private AnyMotionDetector.Vector3 unitYAxis; + private AnyMotionDetector.Vector3 unitZAxis; + private AnyMotionDetector.Vector3 x3; + private AnyMotionDetector.Vector3 case1A; + private AnyMotionDetector.Vector3 case1B; + private AnyMotionDetector.Vector3 case2A; + private AnyMotionDetector.Vector3 case2B; + private AnyMotionDetector.Vector3 x1y1; + private AnyMotionDetector.Vector3 xn1y1; + private AnyMotionDetector.Vector3 x1z1; + private AnyMotionDetector.Vector3 y1z1; + private AnyMotionDetector.Vector3 piOverSixUnitCircle; + + + private boolean nearlyEqual(float a, float b) { + return Math.abs(a - b) <= tolerance; + } + + public void setUp() throws Exception { + super.setUp(); + unitXAxis = new AnyMotionDetector.Vector3(0, 1, 0, 0); + unitYAxis = new AnyMotionDetector.Vector3(0, 0, 1, 0); + unitZAxis = new AnyMotionDetector.Vector3(0, 0, 0, 1); + x3 = new AnyMotionDetector.Vector3(0, 3, 0, 0); + x1y1 = new AnyMotionDetector.Vector3(0, 1, 1, 0); + xn1y1 = new AnyMotionDetector.Vector3(0, -1, 1, 0); + x1z1 = new AnyMotionDetector.Vector3(0, 1, 0, 1); + y1z1 = new AnyMotionDetector.Vector3(0, 0, 1, 1); + piOverSixUnitCircle = new AnyMotionDetector.Vector3( + 0, (float)Math.sqrt(3)/2, (float)0.5, 0); + + case1A = new AnyMotionDetector.Vector3(0, -9.81f, -0.02f, 0.3f); + case1B = new AnyMotionDetector.Vector3(0, -9.80f, -0.02f, 0.3f); + case2A = new AnyMotionDetector.Vector3(0, 1f, 2f, 3f); + case2B = new AnyMotionDetector.Vector3(0, 4f, 5f, 6f); + } + + public void testVector3Norm() { + assertTrue(nearlyEqual(unitXAxis.norm(), 1.0f)); + assertTrue(nearlyEqual(unitYAxis.norm(), 1.0f)); + assertTrue(nearlyEqual(unitZAxis.norm(), 1.0f)); + assertTrue(nearlyEqual(x1y1.norm(), (float)Math.sqrt(2))); + } + + public void testVector3AngleBetween() { + // Zero angle. + assertTrue(nearlyEqual(unitXAxis.angleBetween(unitXAxis), 0.0f)); + assertTrue(nearlyEqual(unitYAxis.angleBetween(unitYAxis), 0.0f)); + assertTrue(nearlyEqual(unitZAxis.angleBetween(unitZAxis), 0.0f)); + + // Unit axes should be perpendicular. + assertTrue(nearlyEqual(unitXAxis.angleBetween(unitYAxis), 90.0f)); + assertTrue(nearlyEqual(unitXAxis.angleBetween(unitZAxis), 90.0f)); + assertTrue(nearlyEqual(unitYAxis.angleBetween(unitZAxis), 90.0f)); + + // 45 degree angles. + assertTrue(nearlyEqual(unitXAxis.angleBetween(x1y1), 45.0f)); + assertTrue(nearlyEqual(unitYAxis.angleBetween(x1y1), 45.0f)); + + // 135 degree angles. + assertTrue(nearlyEqual(xn1y1.angleBetween(unitXAxis), 135.0f)); + + // 30 degree angles. + assertTrue(nearlyEqual(piOverSixUnitCircle.angleBetween(unitXAxis), 30.0f)); + + // These vectors are expected to be still. + assertTrue(case1A.angleBetween(case1A) < STATIONARY_ANGLE_THRESHOLD); + assertTrue(case1A.angleBetween(case1B) < STATIONARY_ANGLE_THRESHOLD); + assertTrue(unitXAxis.angleBetween(unitXAxis) < STATIONARY_ANGLE_THRESHOLD); + assertTrue(unitYAxis.angleBetween(unitYAxis) < STATIONARY_ANGLE_THRESHOLD); + assertTrue(unitZAxis.angleBetween(unitZAxis) < STATIONARY_ANGLE_THRESHOLD); + } + + public void testVector3Normalized() { + AnyMotionDetector.Vector3 unitXAxisNormalized = unitXAxis.normalized(); + assertTrue(nearlyEqual(unitXAxisNormalized.x, unitXAxis.x)); + assertTrue(nearlyEqual(unitXAxisNormalized.y, unitXAxis.y)); + assertTrue(nearlyEqual(unitXAxisNormalized.z, unitXAxis.z)); + + // Normalizing the vector created by multiplying the unit vector by 3 gets the unit vector. + AnyMotionDetector.Vector3 x3Normalized = x3.normalized(); + assertTrue(nearlyEqual(x3Normalized.x, unitXAxis.x)); + assertTrue(nearlyEqual(x3Normalized.y, unitXAxis.y)); + assertTrue(nearlyEqual(x3Normalized.z, unitXAxis.z)); + } + + public void testVector3Cross() { + AnyMotionDetector.Vector3 xCrossX = unitXAxis.cross(unitXAxis); + assertTrue(nearlyEqual(xCrossX.x, 0f)); + assertTrue(nearlyEqual(xCrossX.y, 0f)); + assertTrue(nearlyEqual(xCrossX.z, 0f)); + + AnyMotionDetector.Vector3 xCrossNx = unitXAxis.cross(unitXAxis.times(-1)); + assertTrue(nearlyEqual(xCrossNx.x, 0f)); + assertTrue(nearlyEqual(xCrossNx.y, 0f)); + assertTrue(nearlyEqual(xCrossNx.z, 0f)); + + AnyMotionDetector.Vector3 cross2 = case2A.cross(case2B); + assertTrue(nearlyEqual(cross2.x, -3)); + assertTrue(nearlyEqual(cross2.y, 6)); + assertTrue(nearlyEqual(cross2.z, -3)); + } + + public void testVector3Times() { + AnyMotionDetector.Vector3 yTimes2 = unitYAxis.times(2); + assertTrue(nearlyEqual(yTimes2.x, 0f)); + assertTrue(nearlyEqual(yTimes2.y, 2f)); + assertTrue(nearlyEqual(yTimes2.z, 0f)); + } + + public void testVector3Plus() { + AnyMotionDetector.Vector3 xPlusY = unitXAxis.plus(unitYAxis); + assertTrue(nearlyEqual(xPlusY.x, 1f)); + assertTrue(nearlyEqual(xPlusY.y, 1f)); + assertTrue(nearlyEqual(xPlusY.z, 0f)); + } + + public void testVector3Minus() { + AnyMotionDetector.Vector3 xMinusY = unitXAxis.minus(unitYAxis); + assertTrue(nearlyEqual(xMinusY.x, 1f)); + assertTrue(nearlyEqual(xMinusY.y, -1f)); + assertTrue(nearlyEqual(xMinusY.z, 0f)); + } + + public void testVector3DotProduct() { + float xDotX = unitXAxis.dotProduct(unitXAxis); + float xDotY = unitXAxis.dotProduct(unitYAxis); + float xDotZ = unitXAxis.dotProduct(unitZAxis); + assertTrue(nearlyEqual(xDotX, 1f)); + assertTrue(nearlyEqual(xDotY, 0f)); + assertTrue(nearlyEqual(xDotZ, 0f)); + } +} diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java index 9fcbfe3dd655..0227d27180b5 100644 --- a/telecomm/java/android/telecom/Conference.java +++ b/telecomm/java/android/telecom/Conference.java @@ -82,6 +82,7 @@ public abstract class Conference extends Conferenceable { private StatusHints mStatusHints; private Bundle mExtras; private Set<String> mPreviousExtraKeys; + private final Object mExtrasLock = new Object(); private final Connection.Listener mConnectionDeathListener = new Connection.Listener() { @Override @@ -686,32 +687,35 @@ public abstract class Conference extends Conferenceable { * @param extras The extras associated with this {@code Conference}. */ public final void setExtras(@Nullable Bundle extras) { - // Add/replace any new or changed extras values. - putExtras(extras); - - // If we have used "setExtras" in the past, compare the key set from the last invocation to - // the current one and remove any keys that went away. - if (mPreviousExtraKeys != null) { - List<String> toRemove = new ArrayList<String>(); - for (String oldKey : mPreviousExtraKeys) { - if (extras == null || !extras.containsKey(oldKey)) { - toRemove.add(oldKey); + // Keeping putExtras and removeExtras in the same lock so that this operation happens as a + // block instead of letting other threads put/remove while this method is running. + synchronized (mExtrasLock) { + // Add/replace any new or changed extras values. + putExtras(extras); + // If we have used "setExtras" in the past, compare the key set from the last invocation + // to the current one and remove any keys that went away. + if (mPreviousExtraKeys != null) { + List<String> toRemove = new ArrayList<String>(); + for (String oldKey : mPreviousExtraKeys) { + if (extras == null || !extras.containsKey(oldKey)) { + toRemove.add(oldKey); + } } - } - if (!toRemove.isEmpty()) { - removeExtras(toRemove); + if (!toRemove.isEmpty()) { + removeExtras(toRemove); + } } - } - // Track the keys the last time set called setExtras. This way, the next time setExtras is - // called we can see if the caller has removed any extras values. - if (mPreviousExtraKeys == null) { - mPreviousExtraKeys = new ArraySet<String>(); - } - mPreviousExtraKeys.clear(); - if (extras != null) { - mPreviousExtraKeys.addAll(extras.keySet()); + // Track the keys the last time set called setExtras. This way, the next time setExtras + // is called we can see if the caller has removed any extras values. + if (mPreviousExtraKeys == null) { + mPreviousExtraKeys = new ArraySet<String>(); + } + mPreviousExtraKeys.clear(); + if (extras != null) { + mPreviousExtraKeys.addAll(extras.keySet()); + } } } @@ -730,13 +734,19 @@ public abstract class Conference extends Conferenceable { return; } - if (mExtras == null) { - mExtras = new Bundle(); + // Creating a Bundle clone so we don't have to synchronize on mExtrasLock while calling + // onExtrasChanged. + Bundle listenersBundle; + synchronized (mExtrasLock) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putAll(extras); + listenersBundle = new Bundle(mExtras); } - mExtras.putAll(extras); for (Listener l : mListeners) { - l.onExtrasChanged(this, extras); + l.onExtrasChanged(this, new Bundle(listenersBundle)); } } @@ -790,17 +800,17 @@ public abstract class Conference extends Conferenceable { return; } - if (mExtras != null) { - for (String key : keys) { - mExtras.remove(key); - } - if (mExtras.size() == 0) { - mExtras = null; + synchronized (mExtrasLock) { + if (mExtras != null) { + for (String key : keys) { + mExtras.remove(key); + } } } + List<String> unmodifiableKeys = Collections.unmodifiableList(keys); for (Listener l : mListeners) { - l.onExtrasRemoved(this, keys); + l.onExtrasRemoved(this, unmodifiableKeys); } } @@ -833,7 +843,13 @@ public abstract class Conference extends Conferenceable { * @hide */ final void handleExtrasChanged(Bundle extras) { - mExtras = extras; - onExtrasChanged(mExtras); + Bundle b = null; + synchronized (mExtrasLock) { + mExtras = extras; + if (mExtras != null) { + b = new Bundle(mExtras); + } + } + onExtrasChanged(b); } } diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index ef314f3d139e..ff220f3a4c30 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -1239,6 +1239,7 @@ public abstract class Connection extends Conferenceable { private Conference mConference; private ConnectionService mConnectionService; private Bundle mExtras; + private final Object mExtrasLock = new Object(); /** * Tracks the key set for the extras bundle provided on the last invocation of @@ -1388,7 +1389,13 @@ public abstract class Connection extends Conferenceable { * @return The extras associated with this connection. */ public final Bundle getExtras() { - return mExtras; + Bundle extras = null; + synchronized (mExtrasLock) { + if (mExtras != null) { + extras = new Bundle(mExtras); + } + } + return extras; } /** @@ -1924,14 +1931,20 @@ public abstract class Connection extends Conferenceable { if (extras == null) { return; } - - if (mExtras == null) { - mExtras = new Bundle(); + // Creating a duplicate bundle so we don't have to synchronize on mExtrasLock while calling + // the listeners. + Bundle listenerExtras; + synchronized (mExtrasLock) { + if (mExtras == null) { + mExtras = new Bundle(); + } + mExtras.putAll(extras); + listenerExtras = new Bundle(mExtras); } - mExtras.putAll(extras); - for (Listener l : mListeners) { - l.onExtrasChanged(this, extras); + // Create a new clone of the extras for each listener so that they don't clobber + // each other + l.onExtrasChanged(this, new Bundle(listenerExtras)); } } @@ -1981,18 +1994,16 @@ public abstract class Connection extends Conferenceable { * @hide */ public final void removeExtras(List<String> keys) { - if (mExtras != null) { - for (String key : keys) { - mExtras.remove(key); - } - - if (mExtras.size() == 0) { - mExtras = null; + synchronized (mExtrasLock) { + if (mExtras != null) { + for (String key : keys) { + mExtras.remove(key); + } } } - + List<String> unmodifiableKeys = Collections.unmodifiableList(keys); for (Listener l : mListeners) { - l.onExtrasRemoved(this, keys); + l.onExtrasRemoved(this, unmodifiableKeys); } } @@ -2274,8 +2285,14 @@ public abstract class Connection extends Conferenceable { * @hide */ final void handleExtrasChanged(Bundle extras) { - mExtras = extras; - onExtrasChanged(mExtras); + Bundle b = null; + synchronized (mExtrasLock) { + mExtras = extras; + if (mExtras != null) { + b = new Bundle(mExtras); + } + } + onExtrasChanged(b); } /** |