diff options
37 files changed, 1017 insertions, 148 deletions
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 3f7b29109b3d..507e97e3df05 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -275,7 +275,6 @@ public class DevicePolicyManager { * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li> * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li> * <li>{@link #EXTRA_PROVISIONING_DISCLAIMERS}, optional</li> - * <li>{@link #EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS}, optional</li> * </ul> * * <p>When device owner provisioning has completed, an intent of the type @@ -383,7 +382,6 @@ public class DevicePolicyManager { * <li>{@link #EXTRA_PROVISIONING_ORGANIZATION_NAME}, optional</li> * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li> * <li>{@link #EXTRA_PROVISIONING_USE_MOBILE_DATA}, optional </li> - * <li>{@link #EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS}, optional</li> * </ul> * * @hide @@ -423,7 +421,6 @@ public class DevicePolicyManager { * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li> * <li>{@link #EXTRA_PROVISIONING_LOGO_URI}, optional</li> * <li>{@link #EXTRA_PROVISIONING_MAIN_COLOR}, optional</li> - * <li>{@link #EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS}, optional</li> * </ul> * * <p>When device owner provisioning has completed, an intent of the type @@ -1149,11 +1146,11 @@ public class DevicePolicyManager { * A boolean extra indicating if the education screens from the provisioning flow should be * skipped. If unspecified, defaults to {@code false}. * + * <p>This extra can only be set by the admin app when performing the admin-integrated + * provisioning flow as a result of the {@link #ACTION_GET_PROVISIONING_MODE} activity. + * * <p>If the education screens are skipped, it is the admin application's responsibility * to display its own user education screens. - * - * <p>It can be used when provisioning a fully managed device via - * {@link #ACTION_PROVISION_MANAGED_DEVICE}. */ public static final String EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS = "android.app.extra.PROVISIONING_SKIP_EDUCATION_SCREENS"; @@ -2263,6 +2260,10 @@ public class DevicePolicyManager { * <li>{@link #PROVISIONING_MODE_MANAGED_PROFILE}</li> * </ul> * + * <p>If performing fully-managed device provisioning and the admin app desires to show its + * own education screens, the target activity can additionally return + * {@link #EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS} set to <code>true</code>. + * * <p>The target activity may also return the account that needs to be migrated from primary * user to managed profile in case of a profile owner provisioning in * {@link #EXTRA_PROVISIONING_ACCOUNT_TO_MIGRATE} as result. diff --git a/core/java/android/hardware/SensorAdditionalInfo.java b/core/java/android/hardware/SensorAdditionalInfo.java index 5ff627f48017..12edc5eb7e33 100644 --- a/core/java/android/hardware/SensorAdditionalInfo.java +++ b/core/java/android/hardware/SensorAdditionalInfo.java @@ -119,12 +119,50 @@ public class SensorAdditionalInfo { public static final int TYPE_VEC3_CALIBRATION = 0x10002; /** - * Sensor placement. Describes location and installation angle of the sensor device. + * Sensor placement. * - * Payload: - * floatValues[0..11]: First 3 rows of homogeneous matrix in row major order that describes - * the location and orientation of the sensor. Origin of reference will be the mobile device - * geometric sensor. Reference frame is defined as the same as Android sensor frame. + * Provides the orientation and location of the sensor element in terms of the + * Android coordinate system. This data is given as a 3x4 matrix consisting of a 3x3 rotation + * matrix (R) concatenated with a 3x1 location vector (t). The rotation matrix provides the + * orientation of the Android device coordinate frame relative to the local coordinate frame of + * the sensor. Note that assuming the axes conventions of the sensor are the same as Android, + * this is the inverse of the matrix applied to raw samples read from the sensor to convert them + * into the Android representation. The location vector represents the translation from the + * origin of the Android sensor coordinate system to the geometric center of the sensor, + * specified in millimeters (mm). + * <p> + * <b>Payload</b>: + * <code>floatValues[0..11]</code>: 3x4 matrix in row major order [R; t] + * </p> + * <p> + * <b>Example</b>: + * This raw buffer: <code>{0, 1, 0, 0, -1, 0, 0, 10, 0, 0, 1, -2.5}</code><br> + * Corresponds to this 3x4 matrix: + * <table> + * <thead> + * <tr><td colspan="3">Orientation</td><td>Location</tr> + * </thead> + * <tbody> + * <tr><td>0</td><td>1</td><td>0</td><td>0</td></tr> + * <tr><td>-1</td><td>0</td><td>0</td><td>10</td></tr> + * <tr><td>0</td><td>0</td><td>1</td><td>-2.5</td></tr> + * </tbody> + * </table> + * The sensor is oriented such that: + * <ul> + * <li>The device X axis corresponds to the sensor's local -Y axis + * <li>The device Y axis corresponds to the sensor's local X axis + * <li>The device Z axis and sensor's local Z axis are equivalent + * </ul> + * In other words, if viewing the origin of the Android coordinate system from the positive + * Z direction, the device coordinate frame is to be rotated 90° counter-clockwise about the + * Z axis to align with the sensor's local coordinate frame. Equivalently, a vector in the + * Android coordinate frame may be multiplied with R to rotate it 90° clockwise (270° + * counter-clockwise), yielding its representation in the sensor's coordinate frame. + * Relative to the origin of the Android coordinate system, the physical center of the + * sensor is located 10mm in the positive Y direction, and 2.5mm in the negative Z + * direction. + * </p> */ public static final int TYPE_SENSOR_PLACEMENT = 0x10003; diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index 035061b614f8..702b41beb071 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -330,18 +330,25 @@ public abstract class VibrationEffect implements Parcelable { @TestApi @Nullable public static VibrationEffect get(Uri uri, Context context) { + final ContentResolver cr = context.getContentResolver(); + Uri uncanonicalUri = cr.uncanonicalize(uri); + if (uncanonicalUri == null) { + // If we already had an uncanonical URI, it's possible we'll get null back here. In + // this case, just use the URI as passed in since it wasn't canonicalized in the first + // place. + uncanonicalUri = uri; + } String[] uris = context.getResources().getStringArray( com.android.internal.R.array.config_ringtoneEffectUris); for (int i = 0; i < uris.length && i < RINGTONES.length; i++) { if (uris[i] == null) { continue; } - ContentResolver cr = context.getContentResolver(); Uri mappedUri = cr.uncanonicalize(Uri.parse(uris[i])); if (mappedUri == null) { continue; } - if (mappedUri.equals(uri)) { + if (mappedUri.equals(uncanonicalUri)) { return get(RINGTONES[i]); } } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 0a01beba659f..9573ac0899f5 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -398,6 +398,8 @@ public class ResolverActivity extends Activity { mSystemWindowInsets.bottom)); ((ListView) mAdapterView).addFooterView(mFooterSpacer); + resetButtonBar(); + return insets.consumeSystemWindowInsets(); } diff --git a/core/java/com/android/internal/colorextraction/ColorExtractor.java b/core/java/com/android/internal/colorextraction/ColorExtractor.java index d9fd3b5bd6d8..a6286c0f03f7 100644 --- a/core/java/com/android/internal/colorextraction/ColorExtractor.java +++ b/core/java/com/android/internal/colorextraction/ColorExtractor.java @@ -53,11 +53,13 @@ public class ColorExtractor implements WallpaperManager.OnColorsChangedListener protected WallpaperColors mLockColors; public ColorExtractor(Context context) { - this(context, new Tonal(context), true /* immediately */); + this(context, new Tonal(context), true /* immediately */, + context.getSystemService(WallpaperManager.class)); } @VisibleForTesting - public ColorExtractor(Context context, ExtractionType extractionType, boolean immediately) { + public ColorExtractor(Context context, ExtractionType extractionType, boolean immediately, + WallpaperManager wallpaperManager) { mContext = context; mExtractionType = extractionType; @@ -71,14 +73,8 @@ public class ColorExtractor implements WallpaperManager.OnColorsChangedListener } mOnColorsChangedListeners = new ArrayList<>(); - - WallpaperManager wallpaperManager = mContext.getSystemService(WallpaperManager.class); - if (wallpaperManager == null) { - Log.w(TAG, "Can't listen to color changes!"); - } else { - wallpaperManager.addOnColorsChangedListener(this, null /* handler */); - initExtractColors(wallpaperManager, immediately); - } + wallpaperManager.addOnColorsChangedListener(this, null /* handler */); + initExtractColors(wallpaperManager, immediately); } private void initExtractColors(WallpaperManager wallpaperManager, boolean immediately) { diff --git a/core/java/com/android/internal/os/BinderDeathDispatcher.java b/core/java/com/android/internal/os/BinderDeathDispatcher.java new file mode 100644 index 000000000000..0c93f7f160e4 --- /dev/null +++ b/core/java/com/android/internal/os/BinderDeathDispatcher.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2019 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.os; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.IInterface; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.ArraySet; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; + +import java.io.PrintWriter; + +/** + * Multiplexes multiple binder death recipients on the same binder objects, so that at the native + * level, we only need to keep track of one death recipient reference. This will help reduce the + * number of JNI strong references. + * + * test with: atest FrameworksCoreTests:BinderDeathDispatcherTest + */ +public class BinderDeathDispatcher<T extends IInterface> { + private static final String TAG = "BinderDeathDispatcher"; + + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private final ArrayMap<IBinder, RecipientsInfo> mTargets = new ArrayMap<>(); + + @VisibleForTesting + class RecipientsInfo implements DeathRecipient { + final IBinder mTarget; + + /** + * Recipient list. If it's null, {@link #mTarget} has already died, but in that case + * this RecipientsInfo instance is removed from {@link #mTargets}. + */ + @GuardedBy("mLock") + @Nullable + ArraySet<DeathRecipient> mRecipients = new ArraySet<>(); + + private RecipientsInfo(IBinder target) { + mTarget = target; + } + + @Override + public void binderDied() { + final ArraySet<DeathRecipient> copy; + synchronized (mLock) { + copy = mRecipients; + mRecipients = null; + + // Also remove from the targets. + mTargets.remove(mTarget); + } + if (copy == null) { + return; + } + // Let's call it without holding the lock. + final int size = copy.size(); + for (int i = 0; i < size; i++) { + copy.valueAt(i).binderDied(); + } + } + } + + /** + * Add a {@code recipient} to the death recipient list on {@code target}. + * + * @return # of recipients in the recipient list, including {@code recipient}. Or, -1 + * if {@code target} is already dead, in which case recipient's + * {@link DeathRecipient#binderDied} won't be called. + */ + public int linkToDeath(@NonNull T target, @NonNull DeathRecipient recipient) { + final IBinder ib = target.asBinder(); + synchronized (mLock) { + RecipientsInfo info = mTargets.get(ib); + if (info == null) { + info = new RecipientsInfo(ib); + + // First recipient; need to link to death. + try { + ib.linkToDeath(info, 0); + } catch (RemoteException e) { + return -1; // Already dead. + } + mTargets.put(ib, info); + } + info.mRecipients.add(recipient); + return info.mRecipients.size(); + } + } + + public void unlinkToDeath(@NonNull T target, @NonNull DeathRecipient recipient) { + final IBinder ib = target.asBinder(); + + synchronized (mLock) { + final RecipientsInfo info = mTargets.get(ib); + if (info == null) { + return; + } + if (info.mRecipients.remove(recipient) && info.mRecipients.size() == 0) { + info.mTarget.unlinkToDeath(info, 0); + mTargets.remove(info.mTarget); + } + } + } + + public void dump(PrintWriter pw, String indent) { + synchronized (mLock) { + pw.print(indent); + pw.print("# of watched binders: "); + pw.println(mTargets.size()); + + pw.print(indent); + pw.print("# of death recipients: "); + int n = 0; + for (RecipientsInfo info : mTargets.values()) { + n += info.mRecipients.size(); + } + pw.println(n); + } + } + + @VisibleForTesting + public ArrayMap<IBinder, RecipientsInfo> getTargetsForTest() { + return mTargets; + } +} diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index e8691fa5e23e..b73ecd1974aa 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -756,4 +756,8 @@ public class ArrayUtils { return String.valueOf(value); } } + + public static @Nullable <T> T firstOrNull(T[] items) { + return items.length > 0 ? items[0] : null; + } } diff --git a/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java new file mode 100644 index 000000000000..59148870fa3c --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/BinderDeathDispatcherTest.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2019 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.os; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.os.DeadObjectException; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.IInterface; +import android.os.Parcel; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.FileDescriptor; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BinderDeathDispatcherTest { + private static class MyTarget implements IInterface, IBinder { + public boolean isAlive = true; + public DeathRecipient mRecipient; + + @Override + public String getInterfaceDescriptor() throws RemoteException { + return null; + } + + @Override + public boolean pingBinder() { + return false; + } + + @Override + public boolean isBinderAlive() { + return isAlive; + } + + @Override + public IInterface queryLocalInterface(String descriptor) { + return null; + } + + @Override + public void dump(FileDescriptor fd, String[] args) throws RemoteException { + + } + + @Override + public void dumpAsync(FileDescriptor fd, String[] args) throws RemoteException { + + } + + @Override + public void shellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ShellCallback shellCallback, ResultReceiver resultReceiver) + throws RemoteException { + + } + + @Override + public boolean transact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + return false; + } + + @Override + public void linkToDeath(DeathRecipient recipient, int flags) throws RemoteException { + // In any situation, a single binder object should only have at most one death + // recipient. + assertThat(mRecipient).isNull(); + + if (!isAlive) { + throw new DeadObjectException(); + } + + mRecipient = recipient; + } + + @Override + public boolean unlinkToDeath(DeathRecipient recipient, int flags) { + if (!isAlive) { + return false; + } + assertThat(mRecipient).isSameAs(recipient); + mRecipient = null; + return true; + } + + @Override + public IBinder asBinder() { + return this; + } + + public void die() { + isAlive = false; + if (mRecipient != null) { + mRecipient.binderDied(); + } + mRecipient = null; + } + + public boolean hasDeathRecipient() { + return mRecipient != null; + } + } + + @Test + public void testRegisterAndUnregister() { + BinderDeathDispatcher<MyTarget> d = new BinderDeathDispatcher<>(); + + MyTarget t1 = new MyTarget(); + MyTarget t2 = new MyTarget(); + MyTarget t3 = new MyTarget(); + + DeathRecipient r1 = mock(DeathRecipient.class); + DeathRecipient r2 = mock(DeathRecipient.class); + DeathRecipient r3 = mock(DeathRecipient.class); + DeathRecipient r4 = mock(DeathRecipient.class); + DeathRecipient r5 = mock(DeathRecipient.class); + + // Start hooking up. + + // Link 3 recipients to t1 -- only one real recipient will be set. + assertThat(d.linkToDeath(t1, r1)).isEqualTo(1); + assertThat(d.getTargetsForTest().size()).isEqualTo(1); + + assertThat(d.linkToDeath(t1, r2)).isEqualTo(2); + assertThat(d.linkToDeath(t1, r3)).isEqualTo(3); + assertThat(d.getTargetsForTest().size()).isEqualTo(1); + + // Unlink two -- the real recipient is still set. + d.unlinkToDeath(t1, r1); + d.unlinkToDeath(t1, r2); + + assertThat(t1.hasDeathRecipient()).isTrue(); + assertThat(d.getTargetsForTest().size()).isEqualTo(1); + + // Unlink the last one. The real recipient is also unlinked. + d.unlinkToDeath(t1, r3); + assertThat(t1.hasDeathRecipient()).isFalse(); + assertThat(d.getTargetsForTest().size()).isEqualTo(0); + + // Set recipients to t1, t2 and t3. t3 has two. + assertThat(d.linkToDeath(t1, r1)).isEqualTo(1); + assertThat(d.linkToDeath(t2, r1)).isEqualTo(1); + assertThat(d.linkToDeath(t3, r1)).isEqualTo(1); + assertThat(d.linkToDeath(t3, r2)).isEqualTo(2); + + + // They should all have a real recipient. + assertThat(t1.hasDeathRecipient()).isTrue(); + assertThat(t2.hasDeathRecipient()).isTrue(); + assertThat(t3.hasDeathRecipient()).isTrue(); + + assertThat(d.getTargetsForTest().size()).isEqualTo(3); + + // Unlink r1 from t3. t3 still has r2, so it should still have a real recipient. + d.unlinkToDeath(t3, r1); + assertThat(t1.hasDeathRecipient()).isTrue(); + assertThat(t2.hasDeathRecipient()).isTrue(); + assertThat(t3.hasDeathRecipient()).isTrue(); + assertThat(d.getTargetsForTest().size()).isEqualTo(3); + + // Unlink r2 from t3. Now t3 has no real recipient. + d.unlinkToDeath(t3, r2); + assertThat(t3.hasDeathRecipient()).isFalse(); + assertThat(d.getTargetsForTest().size()).isEqualTo(2); + } + + @Test + public void testRegisterAndKill() { + BinderDeathDispatcher<MyTarget> d = new BinderDeathDispatcher<>(); + + MyTarget t1 = new MyTarget(); + MyTarget t2 = new MyTarget(); + MyTarget t3 = new MyTarget(); + + DeathRecipient r1 = mock(DeathRecipient.class); + DeathRecipient r2 = mock(DeathRecipient.class); + DeathRecipient r3 = mock(DeathRecipient.class); + DeathRecipient r4 = mock(DeathRecipient.class); + DeathRecipient r5 = mock(DeathRecipient.class); + + // Hook them up. + + d.linkToDeath(t1, r1); + d.linkToDeath(t1, r2); + d.linkToDeath(t1, r3); + + // r4 is linked then unlinked. It shouldn't be notified. + d.linkToDeath(t1, r4); + d.unlinkToDeath(t1, r4); + + d.linkToDeath(t2, r1); + + d.linkToDeath(t3, r3); + d.linkToDeath(t3, r5); + + assertThat(d.getTargetsForTest().size()).isEqualTo(3); + + // Kill the targets. + + t1.die(); + verify(r1, times(1)).binderDied(); + verify(r2, times(1)).binderDied(); + verify(r3, times(1)).binderDied(); + verify(r4, times(0)).binderDied(); + verify(r5, times(0)).binderDied(); + + assertThat(d.getTargetsForTest().size()).isEqualTo(2); + + reset(r1, r2, r3, r4, r5); + + t2.die(); + verify(r1, times(1)).binderDied(); + verify(r2, times(0)).binderDied(); + verify(r3, times(0)).binderDied(); + verify(r4, times(0)).binderDied(); + verify(r5, times(0)).binderDied(); + + assertThat(d.getTargetsForTest().size()).isEqualTo(1); + + reset(r1, r2, r3, r4, r5); + + t3.die(); + verify(r1, times(0)).binderDied(); + verify(r2, times(0)).binderDied(); + verify(r3, times(1)).binderDied(); + verify(r4, times(0)).binderDied(); + verify(r5, times(1)).binderDied(); + + assertThat(d.getTargetsForTest().size()).isEqualTo(0); + + // Try to register to a dead object -> should return -1. + assertThat(d.linkToDeath(t1, r1)).isEqualTo(-1); + + assertThat(d.getTargetsForTest().size()).isEqualTo(0); + } +} diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 3fe24929a778..fdf0bc949ae0 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -953,8 +953,8 @@ <!-- Shows to explain the double tap interaction with notifications: After tapping a notification on Keyguard, this will explain users to tap again to launch a notification. [CHAR LIMIT=60] --> <string name="notification_tap_again">Tap again to open</string> - <!-- Shows when people have pressed the unlock icon to explain how to unlock. [CHAR LIMIT=60] --> - <string name="keyguard_unlock">Swipe up to unlock</string> + <!-- Message shown when lock screen is tapped or face authentication fails. [CHAR LIMIT=60] --> + <string name="keyguard_unlock">Swipe up to open</string> <!-- Text on keyguard screen and in Quick Settings footer indicating that the device is enterprise-managed by a Device Owner [CHAR LIMIT=60] --> <string name="do_disclosure_generic">This device is managed by your organization</string> diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 5e192193a87a..b7bb4d9b787f 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -77,8 +77,10 @@ import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentHostManager.FragmentListener; import com.android.systemui.plugins.qs.QS; import com.android.systemui.qs.SecureSetting; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.NavigationBarTransitions; +import com.android.systemui.statusbar.phone.NavigationModeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.tuner.TunablePadding; import com.android.systemui.tuner.TunerService; @@ -125,6 +127,7 @@ public class ScreenDecorations extends SystemUI implements Tunable, private Handler mHandler; private boolean mAssistHintBlocked = false; private boolean mIsReceivingNavBarColor = false; + private boolean mInGesturalMode; /** * Converts a set of {@link Rect}s into a {@link Region} @@ -149,6 +152,28 @@ public class ScreenDecorations extends SystemUI implements Tunable, mHandler.post(this::startOnScreenDecorationsThread); setupStatusBarPaddingIfNeeded(); putComponent(ScreenDecorations.class, this); + mInGesturalMode = QuickStepContract.isGesturalMode( + Dependency.get(NavigationModeController.class) + .addListener(this::handleNavigationModeChange)); + } + + @VisibleForTesting + void handleNavigationModeChange(int navigationMode) { + if (!mHandler.getLooper().isCurrentThread()) { + mHandler.post(() -> handleNavigationModeChange(navigationMode)); + return; + } + boolean inGesturalMode = QuickStepContract.isGesturalMode(navigationMode); + if (mInGesturalMode != inGesturalMode) { + mInGesturalMode = inGesturalMode; + + if (mInGesturalMode && mOverlay == null) { + setupDecorations(); + if (mOverlay != null) { + updateLayoutParams(); + } + } + } } private void fade(View view, boolean fadeIn, boolean isLeft) { @@ -232,6 +257,7 @@ public class ScreenDecorations extends SystemUI implements Tunable, break; } } + updateWindowVisibilities(); } /** @@ -256,11 +282,15 @@ public class ScreenDecorations extends SystemUI implements Tunable, return thread.getThreadHandler(); } + private boolean shouldHostHandles() { + return mInGesturalMode; + } + private void startOnScreenDecorationsThread() { mRotation = RotationUtils.getExactRotation(mContext); mWindowManager = mContext.getSystemService(WindowManager.class); updateRoundedCornerRadii(); - if (hasRoundedCorners() || shouldDrawCutout()) { + if (hasRoundedCorners() || shouldDrawCutout() || shouldHostHandles()) { setupDecorations(); } @@ -565,7 +595,10 @@ public class ScreenDecorations extends SystemUI implements Tunable, boolean visibleForCutout = shouldDrawCutout() && overlay.findViewById(R.id.display_cutout).getVisibility() == View.VISIBLE; boolean visibleForRoundedCorners = hasRoundedCorners(); - overlay.setVisibility(visibleForCutout || visibleForRoundedCorners + boolean visibleForHandles = overlay.findViewById(R.id.assist_hint_left).getVisibility() + == View.VISIBLE || overlay.findViewById(R.id.assist_hint_right).getVisibility() + == View.VISIBLE; + overlay.setVisibility(visibleForCutout || visibleForRoundedCorners || visibleForHandles ? View.VISIBLE : View.GONE); } @@ -688,10 +721,6 @@ public class ScreenDecorations extends SystemUI implements Tunable, setSize(mOverlay.findViewById(R.id.right), sizeTop); setSize(mBottomOverlay.findViewById(R.id.left), sizeBottom); setSize(mBottomOverlay.findViewById(R.id.right), sizeBottom); - setSize(mOverlay.findViewById(R.id.assist_hint_left), sizeTop * 2); - setSize(mOverlay.findViewById(R.id.assist_hint_right), sizeTop * 2); - setSize(mBottomOverlay.findViewById(R.id.assist_hint_left), sizeBottom * 2); - setSize(mBottomOverlay.findViewById(R.id.assist_hint_right), sizeBottom * 2); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java index 5717a54fd8a0..499fe8d47345 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java @@ -16,6 +16,8 @@ package com.android.systemui.biometrics; +import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE; + import android.app.admin.DevicePolicyManager; import android.content.Context; import android.graphics.PixelFormat; @@ -36,6 +38,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; import android.view.animation.Interpolator; import android.widget.Button; import android.widget.ImageView; @@ -280,6 +283,7 @@ public abstract class BiometricDialogView extends LinearLayout { final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE); if (TextUtils.isEmpty(subtitleText)) { mSubtitleText.setVisibility(View.GONE); + announceAccessibilityEvent(); } else { mSubtitleText.setVisibility(View.VISIBLE); mSubtitleText.setText(subtitleText); @@ -289,6 +293,7 @@ public abstract class BiometricDialogView extends LinearLayout { mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION); if (TextUtils.isEmpty(descriptionText)) { mDescriptionText.setVisibility(View.GONE); + announceAccessibilityEvent(); } else { mDescriptionText.setVisibility(View.VISIBLE); mDescriptionText.setText(descriptionText); @@ -447,12 +452,14 @@ public abstract class BiometricDialogView extends LinearLayout { if (newState == STATE_PENDING_CONFIRMATION) { mHandler.removeMessages(MSG_RESET_MESSAGE); mErrorText.setVisibility(View.INVISIBLE); + announceAccessibilityEvent(); mPositiveButton.setVisibility(View.VISIBLE); mPositiveButton.setEnabled(true); } else if (newState == STATE_AUTHENTICATED) { mPositiveButton.setVisibility(View.GONE); mNegativeButton.setVisibility(View.GONE); mErrorText.setVisibility(View.INVISIBLE); + announceAccessibilityEvent(); } if (newState == STATE_PENDING_CONFIRMATION || newState == STATE_AUTHENTICATED) { @@ -471,14 +478,20 @@ public abstract class BiometricDialogView extends LinearLayout { public void restoreState(Bundle bundle) { mRestoredState = bundle; - mTryAgainButton.setVisibility(bundle.getInt(KEY_TRY_AGAIN_VISIBILITY)); - mPositiveButton.setVisibility(bundle.getInt(KEY_CONFIRM_VISIBILITY)); + final int tryAgainVisibility = bundle.getInt(KEY_TRY_AGAIN_VISIBILITY); + mTryAgainButton.setVisibility(tryAgainVisibility); + final int confirmVisibility = bundle.getInt(KEY_CONFIRM_VISIBILITY); + mPositiveButton.setVisibility(confirmVisibility); mState = bundle.getInt(KEY_STATE); mErrorText.setText(bundle.getCharSequence(KEY_ERROR_TEXT_STRING)); mErrorText.setContentDescription(bundle.getCharSequence(KEY_ERROR_TEXT_STRING)); - mErrorText.setVisibility(bundle.getInt(KEY_ERROR_TEXT_VISIBILITY)); + final int errorTextVisibility = bundle.getInt(KEY_ERROR_TEXT_VISIBILITY); + mErrorText.setVisibility(errorTextVisibility); + if (errorTextVisibility == View.INVISIBLE || tryAgainVisibility == View.INVISIBLE + || confirmVisibility == View.INVISIBLE) { + announceAccessibilityEvent(); + } mErrorText.setTextColor(bundle.getInt(KEY_ERROR_TEXT_COLOR)); - if (bundle.getBoolean(KEY_ERROR_TEXT_IS_TEMPORARY)) { mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RESET_MESSAGE), BiometricPrompt.HIDE_DIALOG_DELAY); @@ -501,4 +514,16 @@ public abstract class BiometricDialogView extends LinearLayout { lp.token = mWindowToken; return lp; } + + // Every time a view becomes invisible we need to announce an accessibility event. + // This is due to an issue in the framework, b/132298701 recommended this workaround. + protected void announceAccessibilityEvent() { + AccessibilityEvent event = AccessibilityEvent.obtain(); + event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); + event.setContentChangeTypes(CONTENT_CHANGE_TYPE_SUBTREE); + mDialog.sendAccessibilityEventUnchecked(event); + mDialog.notifySubtreeAccessibilityStateChanged(mDialog, mDialog, + CONTENT_CHANGE_TYPE_SUBTREE); + event.recycle(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java index 8f26f1847779..91124cb443be 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java @@ -149,6 +149,7 @@ public class FaceDialogView extends BiometricDialogView { private final Runnable mErrorToIdleAnimationRunnable = () -> { updateState(STATE_IDLE); mErrorText.setVisibility(View.INVISIBLE); + announceAccessibilityEvent(); }; public FaceDialogView(Context context, @@ -188,6 +189,7 @@ public class FaceDialogView extends BiometricDialogView { mDialog.invalidateOutline(); mSize = newSize; + announceAccessibilityEvent(); } else if (mSize == SIZE_SMALL && newSize == SIZE_BIG) { mSize = SIZE_GROWING; @@ -294,6 +296,7 @@ public class FaceDialogView extends BiometricDialogView { mErrorText.setVisibility(View.VISIBLE); } else { mErrorText.setVisibility(View.INVISIBLE); + announceAccessibilityEvent(); } } @@ -368,11 +371,13 @@ public class FaceDialogView extends BiometricDialogView { mTryAgainButton.setVisibility(View.VISIBLE); } else { mTryAgainButton.setVisibility(View.GONE); + announceAccessibilityEvent(); } } if (show) { mPositiveButton.setVisibility(View.GONE); + announceAccessibilityEvent(); } } diff --git a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java index 835ffc976e9f..6579c0b6de27 100644 --- a/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java +++ b/packages/SystemUI/src/com/android/systemui/colorextraction/SysuiColorExtractor.java @@ -59,13 +59,15 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable, @Inject public SysuiColorExtractor(Context context, ConfigurationController configurationController) { - this(context, new Tonal(context), configurationController, true); + this(context, new Tonal(context), configurationController, true, + context.getSystemService(WallpaperManager.class)); } @VisibleForTesting public SysuiColorExtractor(Context context, ExtractionType type, - ConfigurationController configurationController, boolean registerVisibility) { - super(context, type, false /* immediately */); + ConfigurationController configurationController, boolean registerVisibility, + WallpaperManager wallpaperManager) { + super(context, type, false /* immediately */, wallpaperManager); mTonal = type instanceof Tonal ? (Tonal) type : new Tonal(context); mWpHiddenColors = new GradientColors(); configurationController.addCallback(this); @@ -91,13 +93,10 @@ public class SysuiColorExtractor extends ColorExtractor implements Dumpable, } } - WallpaperManager wallpaperManager = context.getSystemService(WallpaperManager.class); - if (wallpaperManager != null) { - // Listen to all users instead of only the current one. - wallpaperManager.removeOnColorsChangedListener(this); - wallpaperManager.addOnColorsChangedListener(this, null /* handler */, - UserHandle.USER_ALL); - } + // Listen to all users instead of only the current one. + wallpaperManager.removeOnColorsChangedListener(this); + wallpaperManager.addOnColorsChangedListener(this, null /* handler */, + UserHandle.USER_ALL); } private void updateDefaultGradients(WallpaperColors colors) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java index 0f6740d44d04..7c6c556b5241 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java @@ -569,16 +569,21 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G switch (behavior) { case BEHAVIOR_ALERTING: - alert.setSelected(true); - silence.setSelected(false); mPriorityDescriptionView.setVisibility(VISIBLE); mSilentDescriptionView.setVisibility(GONE); + post(() -> { + alert.setSelected(true); + silence.setSelected(false); + }); break; case BEHAVIOR_SILENT: - alert.setSelected(false); - silence.setSelected(true); + mSilentDescriptionView.setVisibility(VISIBLE); mPriorityDescriptionView.setVisibility(GONE); + post(() -> { + alert.setSelected(false); + silence.setSelected(true); + }); break; default: throw new IllegalArgumentException("Unrecognized alerting behavior: " + behavior); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index b3cbf083e4a8..1d1ae3746b38 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -686,6 +686,7 @@ public class NotificationPanelView extends PanelView implements mClockPositionResult.clockX, CLOCK_ANIMATION_PROPERTIES, animateClock); PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.Y, mClockPositionResult.clockY, CLOCK_ANIMATION_PROPERTIES, animateClock); + updateNotificationTranslucency(); updateClock(); stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 9536be3d5900..8ad25bfc1f4d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -1523,6 +1523,8 @@ public class StatusBar extends SystemUI implements DemoMode, @Override // UnlockMethodCache.OnUnlockMethodChangedListener public void onUnlockMethodStateChanged() { + // Unlock method state changed. Notify KeguardMonitor + updateKeyguardState(); logStateToEventlog(); } @@ -3419,9 +3421,7 @@ public class StatusBar extends SystemUI implements DemoMode, checkBarModes(); updateScrimController(); mPresenter.updateMediaMetaData(false, mState != StatusBarState.KEYGUARD); - mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(), - mUnlockMethodCache.isMethodSecure(), - mStatusBarKeyguardViewManager.isOccluded()); + updateKeyguardState(); Trace.endSection(); } @@ -3460,6 +3460,12 @@ public class StatusBar extends SystemUI implements DemoMode, mStatusBarStateController.setIsDozing(dozing); } + private void updateKeyguardState() { + mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(), + mUnlockMethodCache.isMethodSecure(), + mStatusBarKeyguardViewManager.isOccluded()); + } + public void onActivationReset() { mKeyguardIndicationController.hideTransientIndication(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 64ab060e14a2..3b5e12c4ef96 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -45,6 +45,7 @@ import android.testing.TestableLooper.RunWithLooper; import android.view.Display; import android.view.View; import android.view.WindowManager; +import android.view.WindowManagerPolicyConstants; import androidx.test.filters.SmallTest; @@ -52,6 +53,7 @@ import com.android.systemui.R.dimen; import com.android.systemui.ScreenDecorations.TunablePaddingTagListener; import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentService; +import com.android.systemui.statusbar.phone.NavigationModeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarWindowView; import com.android.systemui.tuner.TunablePadding; @@ -78,6 +80,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { private TunerService mTunerService; private StatusBarWindowView mView; private TunablePaddingService mTunablePaddingService; + private NavigationModeController mNavigationModeController; @Before public void setup() { @@ -87,6 +90,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { mTunablePaddingService = mDependency.injectMockDependency(TunablePaddingService.class); mTunerService = mDependency.injectMockDependency(TunerService.class); mFragmentService = mDependency.injectMockDependency(FragmentService.class); + mNavigationModeController = mDependency.injectMockDependency( + NavigationModeController.class); mStatusBar = mock(StatusBar.class); mWindowManager = mock(WindowManager.class); @@ -208,6 +213,54 @@ public class ScreenDecorationsTest extends SysuiTestCase { } @Test + public void testAssistHandles() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.dimen.rounded_corner_radius, 0); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.dimen.rounded_corner_radius_top, 0); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.dimen.rounded_corner_radius_bottom, 0); + mContext.getOrCreateTestableResources() + .addOverride(dimen.rounded_corner_content_padding, 0); + when(mNavigationModeController.addListener(any())).thenReturn( + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL); + + mScreenDecorations.start(); + + // Add 2 windows for rounded corners (top and bottom). + verify(mWindowManager, times(2)).addView(any(), any()); + } + + @Test + public void testDelayedAssistHandles() { + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, false); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.dimen.rounded_corner_radius, 0); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.dimen.rounded_corner_radius_top, 0); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.dimen.rounded_corner_radius_bottom, 0); + mContext.getOrCreateTestableResources() + .addOverride(dimen.rounded_corner_content_padding, 0); + when(mNavigationModeController.addListener(any())).thenReturn( + WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON); + + mScreenDecorations.start(); + + // No handles and no corners + verify(mWindowManager, never()).addView(any(), any()); + + mScreenDecorations.handleNavigationModeChange( + WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL); + + // Add 2 windows for rounded corners (top and bottom). + verify(mWindowManager, times(2)).addView(any(), any()); + } + + @Test public void hasRoundedCornerOverlayFlagSet() { assertThat(mScreenDecorations.getWindowLayoutParams().privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY, diff --git a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java index 9c2c82257173..e3a162c7b10d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java +++ b/packages/SystemUI/tests/src/com/android/systemui/colorextraction/SysuiColorExtractorTests.java @@ -40,6 +40,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** @@ -57,6 +58,8 @@ public class SysuiColorExtractorTests extends SysuiTestCase { ColorExtractor.TYPE_DARK, ColorExtractor.TYPE_EXTRA_DARK}; + @Mock + private WallpaperManager mWallpaperManager; private ColorExtractor.GradientColors mColors; private SysuiColorExtractor mColorExtractor; @@ -72,7 +75,7 @@ public class SysuiColorExtractorTests extends SysuiTestCase { outGradientColorsNormal.set(mColors); outGradientColorsDark.set(mColors); outGradientColorsExtraDark.set(mColors); - }, mock(ConfigurationController.class), false); + }, mock(ConfigurationController.class), false, mWallpaperManager); } @Test @@ -127,7 +130,7 @@ public class SysuiColorExtractorTests extends SysuiTestCase { Tonal tonal = mock(Tonal.class); ConfigurationController configurationController = mock(ConfigurationController.class); SysuiColorExtractor sysuiColorExtractor = new SysuiColorExtractor(getContext(), - tonal, configurationController, false /* registerVisibility */); + tonal, configurationController, false /* registerVisibility */, mWallpaperManager); verify(configurationController).addCallback(eq(sysuiColorExtractor)); reset(tonal); diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java index 770931179c5e..593478c65a1c 100644 --- a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java +++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsPerUserService.java @@ -98,7 +98,7 @@ public final class ContentSuggestionsPerUserService extends RemoteContentSuggestionsService service = getRemoteServiceLocked(); if (service != null) { ActivityManager.TaskSnapshot snapshot = - mActivityTaskManagerInternal.getTaskSnapshot(taskId, false); + mActivityTaskManagerInternal.getTaskSnapshotNoRestore(taskId, false); GraphicBuffer snapshotBuffer = null; int colorSpaceId = 0; if (snapshot != null) { diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 7a2aa7ede466..a6ff37ebf7e1 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -3633,20 +3633,37 @@ public class ConnectivityService extends IConnectivityManager.Stub mNotifier.showNotification(nai.network.netId, type, nai, null, pendingIntent, true); } + private boolean shouldPromptUnvalidated(NetworkAgentInfo nai) { + // Don't prompt if the network is validated, and don't prompt on captive portals + // because we're already prompting the user to sign in. + if (nai.everValidated || nai.everCaptivePortalDetected) { + return false; + } + + // If a network has partial connectivity, always prompt unless the user has already accepted + // partial connectivity and selected don't ask again. This ensures that if the device + // automatically connects to a network that has partial Internet access, the user will + // always be able to use it, either because they've already chosen "don't ask again" or + // because we have prompt them. + if (nai.partialConnectivity && !nai.networkMisc.acceptPartialConnectivity) { + return true; + } + + // If a network has no Internet access, only prompt if the network was explicitly selected + // and if the user has not already told us to use the network regardless of whether it + // validated or not. + if (nai.networkMisc.explicitlySelected && !nai.networkMisc.acceptUnvalidated) { + return true; + } + + return false; + } + private void handlePromptUnvalidated(Network network) { if (VDBG || DDBG) log("handlePromptUnvalidated " + network); NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); - // Only prompt if the network is unvalidated or network has partial internet connectivity - // and was explicitly selected by the user, and if we haven't already been told to switch - // to it regardless of whether it validated or not. Also don't prompt on captive portals - // because we're already prompting the user to sign in. - if (nai == null || nai.everValidated || nai.everCaptivePortalDetected - || !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated - // TODO: Once the value of acceptPartialConnectivity is moved to IpMemoryStore, - // we should reevaluate how to handle acceptPartialConnectivity when network just - // connected. - || nai.networkMisc.acceptPartialConnectivity) { + if (nai == null || !shouldPromptUnvalidated(nai)) { return; } diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java index 3b19d0875f5f..1b45eb4f00bb 100644 --- a/services/core/java/com/android/server/attention/AttentionManagerService.java +++ b/services/core/java/com/android/server/attention/AttentionManagerService.java @@ -190,9 +190,7 @@ public class AttentionManagerService extends SystemService { final UserState userState = getOrCreateCurrentUserStateLocked(); // lazily start the service, which should be very lightweight to start - if (!userState.bindLocked()) { - return false; - } + userState.bindLocked(); // throttle frequent requests final AttentionCheckCache cache = userState.mAttentionCheckCache; @@ -310,7 +308,7 @@ public class AttentionManagerService extends SystemService { protected UserState getOrCreateUserStateLocked(int userId) { UserState result = mUserStates.get(userId); if (result == null) { - result = new UserState(userId, mContext, mLock, mComponentName); + result = new UserState(userId, mContext, mLock, mAttentionHandler, mComponentName); mUserStates.put(userId, result); } return result; @@ -456,31 +454,33 @@ public class AttentionManagerService extends SystemService { @VisibleForTesting protected static class UserState { - final ComponentName mComponentName; - final AttentionServiceConnection mConnection = new AttentionServiceConnection(); + private final ComponentName mComponentName; + private final AttentionServiceConnection mConnection = new AttentionServiceConnection(); @GuardedBy("mLock") IAttentionService mService; @GuardedBy("mLock") - boolean mBinding; - @GuardedBy("mLock") AttentionCheck mCurrentAttentionCheck; @GuardedBy("mLock") AttentionCheckCache mAttentionCheckCache; + @GuardedBy("mLock") + private boolean mBinding; @UserIdInt - final int mUserId; - final Context mContext; - final Object mLock; + private final int mUserId; + private final Context mContext; + private final Object mLock; + private final Handler mAttentionHandler; - UserState(int userId, Context context, Object lock, ComponentName componentName) { + UserState(int userId, Context context, Object lock, Handler handler, + ComponentName componentName) { mUserId = userId; mContext = Preconditions.checkNotNull(context); mLock = Preconditions.checkNotNull(lock); mComponentName = Preconditions.checkNotNull(componentName); + mAttentionHandler = handler; } - @GuardedBy("mLock") private void handlePendingCallbackLocked() { if (!mCurrentAttentionCheck.mIsDispatched) { @@ -499,26 +499,25 @@ public class AttentionManagerService extends SystemService { /** Binds to the system's AttentionService which provides an actual implementation. */ @GuardedBy("mLock") - private boolean bindLocked() { + private void bindLocked() { // No need to bind if service is binding or has already been bound. if (mBinding || mService != null) { - return true; + return; } - final boolean willBind; - final long identity = Binder.clearCallingIdentity(); - - try { - final Intent mServiceIntent = new Intent( + mBinding = true; + // mContext.bindServiceAsUser() calls into ActivityManagerService which it may already + // hold the lock and had called into PowerManagerService, which holds a lock. + // That would create a deadlock. To solve that, putting it on a handler. + mAttentionHandler.post(() -> { + final Intent serviceIntent = new Intent( AttentionService.SERVICE_INTERFACE).setComponent( mComponentName); - willBind = mContext.bindServiceAsUser(mServiceIntent, mConnection, + // Note: no reason to clear the calling identity, we won't have one in a handler. + mContext.bindServiceAsUser(serviceIntent, mConnection, Context.BIND_AUTO_CREATE, UserHandle.CURRENT); - mBinding = willBind; - } finally { - Binder.restoreCallingIdentity(identity); - } - return willBind; + + }); } private void dump(IndentingPrintWriter pw) { @@ -587,6 +586,7 @@ public class AttentionManagerService extends SystemService { super(Looper.myLooper()); } + @Override public void handleMessage(Message msg) { switch (msg.what) { // Do not occupy resources when not in use - unbind proactively. @@ -651,7 +651,12 @@ public class AttentionManagerService extends SystemService { return; } - mContext.unbindService(userState.mConnection); + mAttentionHandler.post(() -> mContext.unbindService(userState.mConnection)); + // Note: this will set mBinding to false even though it could still be trying to bind + // (i.e. the runnable was posted in bindLocked but then cancelAndUnbindLocked was + // called before it's run yet). This is a safe state at the moment, + // since it will eventually, but feels like a source for confusion down the road and + // may cause some expensive and unnecessary work to be done. userState.mConnection.cleanupService(); mUserStates.remove(userState.mUserId); } diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 998ee1ed9ebe..7824a0ac3194 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -22,6 +22,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.job.JobInfo; import android.content.BroadcastReceiver; @@ -56,6 +57,7 @@ import android.os.ShellCallback; import android.os.UserHandle; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.Slog; @@ -63,6 +65,7 @@ import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.BinderDeathDispatcher; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; @@ -83,6 +86,9 @@ public final class ContentService extends IContentService.Stub { static final String TAG = "ContentService"; static final boolean DEBUG = false; + /** Do a WTF if a single observer is registered more than this times. */ + private static final int TOO_MANY_OBSERVERS_THRESHOLD = 1000; + public static class Lifecycle extends SystemService { private ContentService mService; @@ -135,6 +141,12 @@ public final class ContentService extends IContentService.Stub { private SyncManager mSyncManager = null; private final Object mSyncManagerLock = new Object(); + private static final BinderDeathDispatcher<IContentObserver> sObserverDeathDispatcher = + new BinderDeathDispatcher<>(); + + @GuardedBy("sObserverLeakDetectedUid") + private static final ArraySet<Integer> sObserverLeakDetectedUid = new ArraySet<>(0); + /** * Map from userId to providerPackageName to [clientPackageName, uri] to * value. This structure is carefully optimized to keep invalidation logic @@ -236,6 +248,13 @@ public final class ContentService extends IContentService.Stub { pw.println(); pw.print(" Total number of nodes: "); pw.println(counts[0]); pw.print(" Total number of observers: "); pw.println(counts[1]); + + sObserverDeathDispatcher.dump(pw, " "); + } + synchronized (sObserverLeakDetectedUid) { + pw.println(); + pw.print("Observer leaking UIDs: "); + pw.println(sObserverLeakDetectedUid.toString()); } synchronized (mCache) { @@ -1345,18 +1364,40 @@ public final class ContentService extends IContentService.Stub { private final Object observersLock; public ObserverEntry(IContentObserver o, boolean n, Object observersLock, - int _uid, int _pid, int _userHandle) { + int _uid, int _pid, int _userHandle, Uri uri) { this.observersLock = observersLock; observer = o; uid = _uid; pid = _pid; userHandle = _userHandle; notifyForDescendants = n; - try { - observer.asBinder().linkToDeath(this, 0); - } catch (RemoteException e) { + + final int entries = sObserverDeathDispatcher.linkToDeath(observer, this); + if (entries == -1) { binderDied(); + } else if (entries == TOO_MANY_OBSERVERS_THRESHOLD) { + boolean alreadyDetected; + + synchronized (sObserverLeakDetectedUid) { + alreadyDetected = sObserverLeakDetectedUid.contains(uid); + if (!alreadyDetected) { + sObserverLeakDetectedUid.add(uid); + } + } + if (!alreadyDetected) { + String caller = null; + try { + caller = ArrayUtils.firstOrNull(AppGlobals.getPackageManager() + .getPackagesForUid(uid)); + } catch (RemoteException ignore) { + } + Slog.wtf(TAG, "Observer registered too many times. Leak? cpid=" + pid + + " cuid=" + uid + + " cpkg=" + caller + + " url=" + uri); + } } + } @Override @@ -1454,7 +1495,7 @@ public final class ContentService extends IContentService.Stub { // If this is the leaf node add the observer if (index == countUriSegments(uri)) { mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock, - uid, pid, userHandle)); + uid, pid, userHandle, uri)); return; } @@ -1498,7 +1539,7 @@ public final class ContentService extends IContentService.Stub { if (entry.observer.asBinder() == observerBinder) { mObservers.remove(i); // We no longer need to listen for death notifications. Remove it. - observerBinder.unlinkToDeath(entry, 0); + sObserverDeathDispatcher.unlinkToDeath(observer, entry); break; } } diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index bd8859401856..0032e9a8ea51 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -617,7 +617,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements mStagingManager.createSession(session); } - mCallbacks.notifySessionCreated(session.sessionId, session.userId); + if ((session.params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) { + mCallbacks.notifySessionCreated(session.sessionId, session.userId); + } writeSessionsAsync(); return sessionId; } @@ -1210,16 +1212,25 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements class InternalCallback { public void onSessionBadgingChanged(PackageInstallerSession session) { - mCallbacks.notifySessionBadgingChanged(session.sessionId, session.userId); + if ((session.params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) { + mCallbacks.notifySessionBadgingChanged(session.sessionId, session.userId); + } + writeSessionsAsync(); } public void onSessionActiveChanged(PackageInstallerSession session, boolean active) { - mCallbacks.notifySessionActiveChanged(session.sessionId, session.userId, active); + if ((session.params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) { + mCallbacks.notifySessionActiveChanged(session.sessionId, session.userId, + active); + } } public void onSessionProgressChanged(PackageInstallerSession session, float progress) { - mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress); + if ((session.params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) { + mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, + progress); + } } public void onStagedSessionChanged(PackageInstallerSession session) { @@ -1232,7 +1243,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } public void onSessionFinished(final PackageInstallerSession session, boolean success) { - mCallbacks.notifySessionFinished(session.sessionId, session.userId, success); + if ((session.params.installFlags & PackageManager.INSTALL_DRY_RUN) == 0) { + mCallbacks.notifySessionFinished(session.sessionId, session.userId, success); + } mInstallHandler.post(new Runnable() { @Override diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 301f65061045..e107c9aedf38 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -1079,7 +1079,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { if (info.getPackageName().equals(packageName)) { mAppDataRollbackHelper.snapshotAppData(data.info.getRollbackId(), info); saveRollbackData(data); - return; + break; } } } @@ -1091,7 +1091,6 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { mAppDataRollbackHelper.snapshotAppData(rollback.data.info.getRollbackId(), info); saveRollbackData(rollback.data); - return; } } } diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index b3b6efed1a37..4f40dc43c1b7 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -2383,7 +2383,11 @@ class ActivityStack extends ConfigurationContainer { r.setVisible(true); } if (r != starting) { - mStackSupervisor.startSpecificActivityLocked(r, andResume, true /* checkConfig */); + // We should not resume activities that being launched behind because these + // activities are actually behind other fullscreen activities, but still required + // to be visible (such as performing Recents animation). + mStackSupervisor.startSpecificActivityLocked(r, andResume && !r.mLaunchTaskBehind, + true /* checkConfig */); return true; } } @@ -2636,7 +2640,7 @@ class ActivityStack extends ConfigurationContainer { if (!hasRunningActivity) { // There are no activities left in the stack, let's look somewhere else. - return resumeTopActivityInNextFocusableStack(prev, options, "noMoreActivities"); + return resumeNextFocusableActivityWhenStackIsEmpty(prev, options); } next.delayedResume = false; @@ -3037,21 +3041,33 @@ class ActivityStack extends ConfigurationContainer { return true; } - private boolean resumeTopActivityInNextFocusableStack(ActivityRecord prev, - ActivityOptions options, String reason) { - final ActivityStack nextFocusedStack = adjustFocusToNextFocusableStack(reason); - if (nextFocusedStack != null) { - // Try to move focus to the next visible stack with a running activity if this - // stack is not covering the entire screen or is on a secondary display (with no home - // stack). - return mRootActivityContainer.resumeFocusedStacksTopActivities(nextFocusedStack, prev, - null /* targetOptions */); + /** + * Resume the next eligible activity in a focusable stack when this one does not have any + * running activities left. The focus will be adjusted to the next focusable stack and + * top running activities will be resumed in all focusable stacks. However, if the current stack + * is a home stack - we have to keep it focused, start and resume a home activity on the current + * display instead to make sure that the display is not empty. + */ + private boolean resumeNextFocusableActivityWhenStackIsEmpty(ActivityRecord prev, + ActivityOptions options) { + final String reason = "noMoreActivities"; + + if (!isActivityTypeHome()) { + final ActivityStack nextFocusedStack = adjustFocusToNextFocusableStack(reason); + if (nextFocusedStack != null) { + // Try to move focus to the next visible stack with a running activity if this + // stack is not covering the entire screen or is on a secondary display with no home + // stack. + return mRootActivityContainer.resumeFocusedStacksTopActivities(nextFocusedStack, + prev, null /* targetOptions */); + } } - // Let's just start up the Launcher... + // If the current stack is a home stack, or if focus didn't switch to a different stack - + // just start up the Launcher... ActivityOptions.abort(options); if (DEBUG_STATES) Slog.d(TAG_STATES, - "resumeTopActivityInNextFocusableStack: " + reason + ", go home"); + "resumeNextFocusableActivityWhenStackIsEmpty: " + reason + ", go home"); return mRootActivityContainer.resumeHomeActivity(prev, reason, mDisplayId); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index dbc530d38684..ab35652eb525 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -559,7 +559,7 @@ public abstract class ActivityTaskManagerInternal { /** * Gets bitmap snapshot of the provided task id. */ - public abstract ActivityManager.TaskSnapshot getTaskSnapshot(int taskId, + public abstract ActivityManager.TaskSnapshot getTaskSnapshotNoRestore(int taskId, boolean reducedResolution); /** Returns true if uid is considered foreground for activity start purposes. */ diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 8ba7ca1abba0..9fc278e5639d 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -4521,22 +4521,27 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { enforceCallerIsRecentsOrHasPermission(READ_FRAME_BUFFER, "getTaskSnapshot()"); final long ident = Binder.clearCallingIdentity(); try { - final TaskRecord task; - synchronized (mGlobalLock) { - task = mRootActivityContainer.anyTaskForId(taskId, - MATCH_TASK_IN_STACKS_OR_RECENT_TASKS); - if (task == null) { - Slog.w(TAG, "getTaskSnapshot: taskId=" + taskId + " not found"); - return null; - } - } - // Don't call this while holding the lock as this operation might hit the disk. - return task.getSnapshot(reducedResolution); + return getTaskSnapshot(taskId, reducedResolution, true /* restoreFromDisk */); } finally { Binder.restoreCallingIdentity(ident); } } + private ActivityManager.TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution, + boolean restoreFromDisk) { + final TaskRecord task; + synchronized (mGlobalLock) { + task = mRootActivityContainer.anyTaskForId(taskId, + MATCH_TASK_IN_STACKS_OR_RECENT_TASKS); + if (task == null) { + Slog.w(TAG, "getTaskSnapshot: taskId=" + taskId + " not found"); + return null; + } + } + // Don't call this while holding the lock as this operation might hit the disk. + return task.getSnapshot(reducedResolution, restoreFromDisk); + } + @Override public void setDisablePreviewScreenshots(IBinder token, boolean disable) { synchronized (mGlobalLock) { @@ -7419,10 +7424,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public ActivityManager.TaskSnapshot getTaskSnapshot(int taskId, boolean reducedResolution) { - synchronized (mGlobalLock) { - return ActivityTaskManagerService.this.getTaskSnapshot(taskId, reducedResolution); - } + public ActivityManager.TaskSnapshot getTaskSnapshotNoRestore(int taskId, + boolean reducedResolution) { + return ActivityTaskManagerService.this.getTaskSnapshot(taskId, reducedResolution, + false /* restoreFromDisk */); } @Override diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index cae7612e0fcc..4a9a3f71f90e 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -176,6 +176,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree boolean inPendingTransaction; boolean allDrawn; private boolean mLastAllDrawn; + private boolean mUseTransferredAnimation; // Set to true when this app creates a surface while in the middle of an animation. In that // case do not clear allDrawn until the animation completes. @@ -618,9 +619,12 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree boolean runningAppAnimation = false; if (transit != WindowManager.TRANSIT_UNSET) { - if (applyAnimationLocked(lp, transit, visible, isVoiceInteraction)) { - delayed = runningAppAnimation = true; + if (mUseTransferredAnimation) { + runningAppAnimation = isReallyAnimating(); + } else if (applyAnimationLocked(lp, transit, visible, isVoiceInteraction)) { + runningAppAnimation = true; } + delayed = runningAppAnimation; final WindowState window = findMainWindow(); if (window != null && accessibilityController != null) { accessibilityController.onAppWindowTransitionLocked(window, transit); @@ -667,6 +671,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree getDisplayContent().getInputMonitor().updateInputWindowsLw(false /*force*/); } } + mUseTransferredAnimation = false; if (isReallyAnimating()) { delayed = true; @@ -1531,9 +1536,9 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree transferAnimation(fromToken); // When transferring an animation, we no longer need to apply an animation to the - // the token we transfer the animation over. Thus, remove the animation from - // pending opening apps. - getDisplayContent().mOpeningApps.remove(this); + // the token we transfer the animation over. Thus, set this flag to indicate we've + // transferred the animation. + mUseTransferredAnimation = true; mWmService.updateFocusedWindowLocked( UPDATE_FOCUS_WILL_PLACE_SURFACES, true /*updateInputWindows*/); diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java index 298b302a17fb..3fd4e83b9494 100644 --- a/services/core/java/com/android/server/wm/TaskRecord.java +++ b/services/core/java/com/android/server/wm/TaskRecord.java @@ -851,11 +851,12 @@ class TaskRecord extends ConfigurationContainer { /** * DO NOT HOLD THE ACTIVITY MANAGER LOCK WHEN CALLING THIS METHOD! */ - TaskSnapshot getSnapshot(boolean reducedResolution) { + TaskSnapshot getSnapshot(boolean reducedResolution, boolean restoreFromDisk) { // TODO: Move this to {@link TaskWindowContainerController} once recent tasks are more // synchronized between AM and WM. - return mService.mWindowManager.getTaskSnapshot(taskId, userId, reducedResolution); + return mService.mWindowManager.getTaskSnapshot(taskId, userId, reducedResolution, + restoreFromDisk); } void touchActiveTime() { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 0a2fc9b366a3..45c7e693b07c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -3551,8 +3551,9 @@ public class WindowManagerService extends IWindowManager.Stub return true; } - public TaskSnapshot getTaskSnapshot(int taskId, int userId, boolean reducedResolution) { - return mTaskSnapshotController.getSnapshot(taskId, userId, true /* restoreFromDisk */, + public TaskSnapshot getTaskSnapshot(int taskId, int userId, boolean reducedResolution, + boolean restoreFromDisk) { + return mTaskSnapshotController.getSnapshot(taskId, userId, restoreFromDisk, reducedResolution); } diff --git a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java index bb9f49e6f37f..184dc3dfed62 100644 --- a/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/attention/AttentionManagerServiceTest.java @@ -86,6 +86,7 @@ public class AttentionManagerServiceTest { UserState mUserState = new UserState(0, mContext, mLock, + mMockHandler, componentName); mUserState.mService = new MockIAttentionService(); mSpyUserState = spy(mUserState); diff --git a/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java b/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java index 62b0ca805c1b..891ca74a545f 100644 --- a/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java +++ b/services/tests/servicestests/src/com/android/server/content/ObserverNodeTest.java @@ -30,7 +30,7 @@ import com.android.server.content.ContentService.ObserverCall; import com.android.server.content.ContentService.ObserverNode; /** - * bit FrameworksServicesTests:com.android.server.content.ObserverNodeTest + * atest FrameworksServicesTests:com.android.server.content.ObserverNodeTest */ @SmallTest public class ObserverNodeTest extends AndroidTestCase { diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java index 8f41a42bde38..1f8b33eb5bb4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java @@ -24,14 +24,18 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.ActivityStack.ActivityState.PAUSED; import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -107,6 +111,39 @@ public class RecentsAnimationTest extends ActivityTestsBase { } @Test + public void testRestartRecentsActivity() throws Exception { + // Have a recents activity that is not attached to its process (ActivityRecord.app = null). + ActivityDisplay display = mRootActivityContainer.getDefaultDisplay(); + ActivityStack recentsStack = display.createStack(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_RECENTS, true /* onTop */); + ActivityRecord recentActivity = new ActivityBuilder(mService).setComponent( + mRecentsComponent).setCreateTask(true).setStack(recentsStack).build(); + WindowProcessController app = recentActivity.app; + recentActivity.app = null; + + // Start an activity on top. + new ActivityBuilder(mService).setCreateTask(true).build().getActivityStack().moveToFront( + "testRestartRecentsActivity"); + + doCallRealMethod().when(mRootActivityContainer).ensureActivitiesVisible( + any() /* starting */, anyInt() /* configChanges */, + anyBoolean() /* preserveWindows */); + doReturn(app).when(mService).getProcessController(eq(recentActivity.processName), anyInt()); + ClientLifecycleManager lifecycleManager = mService.getLifecycleManager(); + doNothing().when(lifecycleManager).scheduleTransaction(any()); + AppWarnings appWarnings = mService.getAppWarningsLocked(); + spyOn(appWarnings); + doNothing().when(appWarnings).onStartActivity(any()); + + startRecentsActivity(); + + // Recents activity must be restarted, but not be resumed while running recents animation. + verify(mRootActivityContainer.mStackSupervisor).startSpecificActivityLocked( + eq(recentActivity), eq(false), anyBoolean()); + assertThat(recentActivity.getState()).isEqualTo(PAUSED); + } + + @Test public void testSetLaunchTaskBehindOfTargetActivity() { ActivityDisplay display = mRootActivityContainer.getDefaultDisplay(); display.mDisplayContent.mBoundsAnimationController = mock(BoundsAnimationController.class); diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java index c77e25fabf19..8d2c3dd80538 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; @@ -35,6 +36,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityDisplay.POSITION_TOP; import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING; +import static com.android.server.wm.ActivityStackSupervisor.ON_TOP; import static com.android.server.wm.RootActivityContainer.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE; import static org.junit.Assert.assertEquals; @@ -397,6 +399,58 @@ public class RootActivityContainerTests extends ActivityTestsBase { } /** + * Verify that home activity will be started on a display even if another display has a + * focusable activity. + */ + @Test + public void testResumeFocusedStacksStartsHomeActivity_NoActivities() { + mFullscreenStack.remove(); + mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY).getHomeStack().remove(); + mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY) + .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + + doReturn(true).when(mRootActivityContainer).resumeHomeActivity(any(), any(), anyInt()); + + mService.setBooted(true); + + // Trigger resume on all displays + mRootActivityContainer.resumeFocusedStacksTopActivities(); + + // Verify that home activity was started on the default display + verify(mRootActivityContainer).resumeHomeActivity(any(), any(), eq(DEFAULT_DISPLAY)); + } + + /** + * Verify that home activity will be started on a display even if another display has a + * focusable activity. + */ + @Test + public void testResumeFocusedStacksStartsHomeActivity_ActivityOnSecondaryScreen() { + mFullscreenStack.remove(); + mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY).getHomeStack().remove(); + mService.mRootActivityContainer.getActivityDisplay(DEFAULT_DISPLAY) + .createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP); + + // Create an activity on secondary display. + final TestActivityDisplay secondDisplay = addNewActivityDisplayAt( + ActivityDisplay.POSITION_TOP); + final ActivityStack stack = secondDisplay.createStack(WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, true /* onTop */); + final TaskRecord task = new TaskBuilder(mSupervisor).setStack(stack).build(); + new ActivityBuilder(mService).setTask(task).build(); + + doReturn(true).when(mRootActivityContainer).resumeHomeActivity(any(), any(), anyInt()); + + mService.setBooted(true); + + // Trigger resume on all displays + mRootActivityContainer.resumeFocusedStacksTopActivities(); + + // Verify that home activity was started on the default display + verify(mRootActivityContainer).resumeHomeActivity(any(), any(), eq(DEFAULT_DISPLAY)); + } + + /** * Verify that a lingering transition is being executed in case the activity to be resumed is * already resumed */ diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 8ef381d140d6..9806199003d7 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2685,6 +2685,30 @@ public class CarrierConfigManager { public static final String KEY_PREFIX = "gps."; /** + * Location information during (and after) an emergency call is only provided over control + * plane signaling from the network. + * @hide + */ + public static final int SUPL_EMERGENCY_MODE_TYPE_CP_ONLY = 0; + + /** + * Location information during (and after) an emergency call is provided over the data + * plane and serviced by the framework GNSS service, but if it fails, the carrier also + * supports control plane backup signaling. + * @hide + */ + public static final int SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK = 1; + + /** + * Location information during (and after) an emergency call is provided over the data plane + * and serviced by the framework GNSS service only. There is no backup signalling over the + * control plane if it fails. + * @hide + */ + public static final int SUPL_EMERGENCY_MODE_TYPE_DP_ONLY = 2; + + + /** * Determine whether current lpp_mode used for E-911 needs to be kept persistently. * {@code false} - not keeping the lpp_mode means using default configuration of gps.conf * when sim is not presented. @@ -2775,6 +2799,23 @@ public class CarrierConfigManager { */ public static final String KEY_NFW_PROXY_APPS_STRING = KEY_PREFIX + "nfw_proxy_apps"; + /** + * Determines whether or not SUPL ES mode supports a control-plane mechanism to get a user's + * location in the event that data plane SUPL fails or is otherwise unavailable. + * <p> + * An integer value determines the support type of this carrier. If this carrier only + * supports data plane SUPL ES, then the value will be + * {@link #SUPL_EMERGENCY_MODE_TYPE_DP_ONLY}. If the carrier supports control plane fallback + * for emergency SUPL, the value will be {@link #SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK}. + * If the carrier does not support data plane SUPL using the framework, the value will be + * {@link #SUPL_EMERGENCY_MODE_TYPE_CP_ONLY}. + * <p> + * The default value for this configuration is {@link #SUPL_EMERGENCY_MODE_TYPE_CP_ONLY}. + * @hide + */ + public static final String KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT = KEY_PREFIX + + "es_supl_control_plane_support_int"; + private static PersistableBundle getDefaults() { PersistableBundle defaults = new PersistableBundle(); defaults.putBoolean(KEY_PERSIST_LPP_MODE_BOOL, true); @@ -2789,6 +2830,8 @@ public class CarrierConfigManager { defaults.putString(KEY_GPS_LOCK_STRING, "3"); defaults.putString(KEY_ES_EXTENSION_SEC_STRING, "0"); defaults.putString(KEY_NFW_PROXY_APPS_STRING, ""); + defaults.putInt(KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT, + SUPL_EMERGENCY_MODE_TYPE_CP_ONLY); return defaults; } } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index d6011b946139..b7b511e5eb4b 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -1498,6 +1498,48 @@ public class TelephonyManager { */ public static final int EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE_ALL = 4; + /** + * Integer intent extra to be used with {@link #ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED} + * to indicate if the SIM combination in DSDS has limitation or compatible issue. + * e.g. two CDMA SIMs may disrupt each other's voice call in certain scenarios. + * + * @hide + */ + public static final String EXTRA_SIM_COMBINATION_WARNING_TYPE = + "android.telephony.extra.SIM_COMBINATION_WARNING_TYPE"; + + /** @hide */ + @IntDef({ + EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE, + EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SimCombinationWarningType{} + + /** + * Used as an int value for {@link #EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE} + * to indicate there's no SIM combination warning. + * @hide + */ + public static final int EXTRA_SIM_COMBINATION_WARNING_TYPE_NONE = 0; + + /** + * Used as an int value for {@link #EXTRA_DEFAULT_SUBSCRIPTION_SELECT_TYPE} + * to indicate two active SIMs are both CDMA hence there might be functional limitation. + * @hide + */ + public static final int EXTRA_SIM_COMBINATION_WARNING_TYPE_DUAL_CDMA = 1; + + /** + * String intent extra to be used with {@link #ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED} + * to indicate what's the name of SIM combination it has limitation or compatible issue. + * e.g. two CDMA SIMs may disrupt each other's voice call in certain scenarios, and the + * name will be "operator1 & operator2". + * + * @hide + */ + public static final String EXTRA_SIM_COMBINATION_NAMES = + "android.telephony.extra.SIM_COMBINATION_NAMES"; // // // Device Info diff --git a/tests/Internal/src/com/android/internal/colorextraction/ColorExtractorTest.java b/tests/Internal/src/com/android/internal/colorextraction/ColorExtractorTest.java index 17fa93135c7d..f88a7c41c02b 100644 --- a/tests/Internal/src/com/android/internal/colorextraction/ColorExtractorTest.java +++ b/tests/Internal/src/com/android/internal/colorextraction/ColorExtractorTest.java @@ -47,17 +47,19 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class ColorExtractorTest { - Context mContext; + private Context mContext; + private WallpaperManager mWallpaperManager; @Before public void setup() { mContext = InstrumentationRegistry.getContext(); + mWallpaperManager = mock(WallpaperManager.class); } @Test public void ColorExtractor_extractWhenInitialized() { ExtractionType type = mock(Tonal.class); - new ColorExtractor(mContext, type, true); + new ColorExtractor(mContext, type, true, mWallpaperManager); // 1 for lock and 1 for system verify(type, times(2)) .extractInto(any(), any(), any(), any()); @@ -84,7 +86,7 @@ public class ColorExtractorTest { outGradientColorsDark.set(colorsExpectedDark); outGradientColorsExtraDark.set(colorsExpectedExtraDark); }; - ColorExtractor extractor = new ColorExtractor(mContext, type, true); + ColorExtractor extractor = new ColorExtractor(mContext, type, true, mWallpaperManager); GradientColors colors = extractor.getColors(WallpaperManager.FLAG_SYSTEM, ColorExtractor.TYPE_NORMAL); @@ -99,7 +101,8 @@ public class ColorExtractorTest { public void addOnColorsChangedListener_invokesListener() { ColorExtractor.OnColorsChangedListener mockedListeners = mock(ColorExtractor.OnColorsChangedListener.class); - ColorExtractor extractor = new ColorExtractor(mContext, new Tonal(mContext), true); + ColorExtractor extractor = new ColorExtractor(mContext, new Tonal(mContext), true, + mWallpaperManager); extractor.addOnColorsChangedListener(mockedListeners); extractor.onColorsChanged(new WallpaperColors(Color.valueOf(Color.RED), null, null), |