Merge "Added tron metrics for shutdown time" into oc-mr1-dev
diff --git a/api/current.txt b/api/current.txt
index 5acef51..5471084 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -30620,7 +30620,7 @@
field public static final int N = 24; // 0x18
field public static final int N_MR1 = 25; // 0x19
field public static final int O = 26; // 0x1a
- field public static final int O_MR1 = 10000; // 0x2710
+ field public static final int O_MR1 = 27; // 0x1b
}
public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
diff --git a/api/system-current.txt b/api/system-current.txt
index 2eaf544..b7b00f1 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -16561,6 +16561,7 @@
method public android.view.Display getDisplay(int);
method public android.view.Display[] getDisplays();
method public android.view.Display[] getDisplays(java.lang.String);
+ method public android.graphics.Point getStableDisplaySize();
method public void registerDisplayListener(android.hardware.display.DisplayManager.DisplayListener, android.os.Handler);
method public void unregisterDisplayListener(android.hardware.display.DisplayManager.DisplayListener);
field public static final java.lang.String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION";
@@ -33333,7 +33334,7 @@
field public static final int N = 24; // 0x18
field public static final int N_MR1 = 25; // 0x19
field public static final int O = 26; // 0x1a
- field public static final int O_MR1 = 10000; // 0x2710
+ field public static final int O_MR1 = 27; // 0x1b
}
public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
diff --git a/api/test-current.txt b/api/test-current.txt
index 4c6a11b..1708780 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -30764,7 +30764,7 @@
field public static final int N = 24; // 0x18
field public static final int N_MR1 = 25; // 0x19
field public static final int O = 26; // 0x1a
- field public static final int O_MR1 = 10000; // 0x2710
+ field public static final int O_MR1 = 27; // 0x1b
}
public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp
index 107dc86..bf2e45c 100644
--- a/cmds/hid/jni/com_android_commands_hid_Device.cpp
+++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp
@@ -101,16 +101,15 @@
struct uhid_event ev;
memset(&ev, 0, sizeof(ev));
- ev.type = UHID_CREATE2;
- strncpy((char*)ev.u.create2.name, name, UHID_MAX_NAME_LENGTH);
- memcpy(&ev.u.create2.rd_data, descriptor.get(),
- descriptorSize * sizeof(ev.u.create2.rd_data[0]));
- ev.u.create2.rd_size = descriptorSize;
- ev.u.create2.bus = BUS_BLUETOOTH;
- ev.u.create2.vendor = vid;
- ev.u.create2.product = pid;
- ev.u.create2.version = 0;
- ev.u.create2.country = 0;
+ ev.type = UHID_CREATE;
+ strncpy((char*)ev.u.create.name, name, UHID_MAX_NAME_LENGTH);
+ ev.u.create.rd_data = descriptor.get();
+ ev.u.create.rd_size = descriptorSize;
+ ev.u.create.bus = BUS_BLUETOOTH;
+ ev.u.create.vendor = vid;
+ ev.u.create.product = pid;
+ ev.u.create.version = 0;
+ ev.u.create.country = 0;
errno = 0;
ssize_t ret = TEMP_FAILURE_RETRY(::write(fd, &ev, sizeof(ev)));
@@ -159,9 +158,9 @@
void Device::sendReport(uint8_t* report, size_t reportSize) {
struct uhid_event ev;
memset(&ev, 0, sizeof(ev));
- ev.type = UHID_INPUT2;
- ev.u.input2.size = reportSize;
- memcpy(&ev.u.input2.data, report, reportSize);
+ ev.type = UHID_INPUT;
+ ev.u.input.size = reportSize;
+ memcpy(&ev.u.input.data, report, reportSize);
ssize_t ret = TEMP_FAILURE_RETRY(::write(mFd, &ev, sizeof(ev)));
if (ret < 0 || ret != sizeof(ev)) {
LOGE("Failed to send hid event: %s", strerror(errno));
diff --git a/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java b/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java
index b9fedd3..6a4a4be 100644
--- a/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java
+++ b/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java
@@ -34,6 +34,8 @@
" locksettings set-password [--old OLD_CREDENTIAL] NEW_PASSWORD\n" +
" locksettings clear [--old OLD_CREDENTIAL]\n" +
" locksettings verify [--old OLD_CREDENTIAL]\n" +
+ " locksettings set-disabled DISABLED\n" +
+ " locksettings get-disabled\n" +
"\n" +
"flags: \n" +
" --user USER_ID: specify the user, default value is current user\n" +
@@ -50,7 +52,11 @@
"\n" +
"locksettings clear: clears the unlock credential\n" +
"\n" +
- "locksettings verify: verifies the credential and unlocks the user\n";
+ "locksettings verify: verifies the credential and unlocks the user\n" +
+ "\n" +
+ "locksettings set-disabled: sets whether the lock screen should be disabled\n" +
+ "\n" +
+ "locksettings get-disabled: retrieves whether the lock screen is disabled\n";
public static void main(String[] args) {
(new LockSettingsCmd()).run(args);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 757795e..72d5ede 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -7033,6 +7033,7 @@
}
final void performRestart() {
+ mCanEnterPictureInPicture = true;
mFragments.noteStateNotSaved();
if (mToken != null && mParent == null) {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 6dead3e..0bffc9e 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -195,6 +195,14 @@
private static final String KEY_DOCK_CREATE_MODE = "android:activity.dockCreateMode";
/**
+ * Determines whether to disallow the outgoing activity from entering picture-in-picture as the
+ * result of a new activity being launched.
+ * @hide
+ */
+ private static final String KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING =
+ "android:activity.disallowEnterPictureInPictureWhileLaunching";
+
+ /**
* For Activity transitions, the calling Activity's TransitionListener used to
* notify the called Activity when the shared element and the exit transitions
* complete.
@@ -267,6 +275,7 @@
private int mLaunchStackId = INVALID_STACK_ID;
private int mLaunchTaskId = -1;
private int mDockCreateMode = DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
+ private boolean mDisallowEnterPictureInPictureWhileLaunching;
private boolean mTaskOverlay;
private boolean mTaskOverlayCanResume;
private AppTransitionAnimationSpec mAnimSpecs[];
@@ -856,6 +865,8 @@
mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false);
mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false);
mDockCreateMode = opts.getInt(KEY_DOCK_CREATE_MODE, DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT);
+ mDisallowEnterPictureInPictureWhileLaunching = opts.getBoolean(
+ KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, false);
if (opts.containsKey(KEY_ANIM_SPECS)) {
Parcelable[] specs = opts.getParcelableArray(KEY_ANIM_SPECS);
mAnimSpecs = new AppTransitionAnimationSpec[specs.length];
@@ -1121,6 +1132,16 @@
mDockCreateMode = dockCreateMode;
}
+ /** @hide */
+ public void setDisallowEnterPictureInPictureWhileLaunching(boolean disallow) {
+ mDisallowEnterPictureInPictureWhileLaunching = disallow;
+ }
+
+ /** @hide */
+ public boolean disallowEnterPictureInPictureWhileLaunching() {
+ return mDisallowEnterPictureInPictureWhileLaunching;
+ }
+
/**
* Update the current values in this ActivityOptions from those supplied
* in <var>otherOptions</var>. Any values
@@ -1275,6 +1296,8 @@
b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay);
b.putBoolean(KEY_TASK_OVERLAY_CAN_RESUME, mTaskOverlayCanResume);
b.putInt(KEY_DOCK_CREATE_MODE, mDockCreateMode);
+ b.putBoolean(KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING,
+ mDisallowEnterPictureInPictureWhileLaunching);
if (mAnimSpecs != null) {
b.putParcelableArray(KEY_ANIM_SPECS, mAnimSpecs);
}
diff --git a/core/java/android/app/VrManager.java b/core/java/android/app/VrManager.java
index b40c96c..363e20a 100644
--- a/core/java/android/app/VrManager.java
+++ b/core/java/android/app/VrManager.java
@@ -1,13 +1,18 @@
package android.app;
-
+import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.content.ComponentName;
import android.content.Context;
+import android.os.Handler;
import android.os.RemoteException;
+import android.service.vr.IPersistentVrStateCallbacks;
import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
+import android.util.ArrayMap;
+
+import java.util.Map;
/**
* Used to control aspects of a devices Virtual Reality (VR) capabilities.
@@ -16,7 +21,33 @@
@SystemApi
@SystemService(Context.VR_SERVICE)
public class VrManager {
+
+ private static class CallbackEntry {
+ final IVrStateCallbacks mStateCallback = new IVrStateCallbacks.Stub() {
+ @Override
+ public void onVrStateChanged(boolean enabled) {
+ mHandler.post(() -> mCallback.onVrStateChanged(enabled));
+ }
+
+ };
+ final IPersistentVrStateCallbacks mPersistentStateCallback =
+ new IPersistentVrStateCallbacks.Stub() {
+ @Override
+ public void onPersistentVrStateChanged(boolean enabled) {
+ mHandler.post(() -> mCallback.onPersistentVrStateChanged(enabled));
+ }
+ };
+ final VrStateCallback mCallback;
+ final Handler mHandler;
+
+ CallbackEntry(VrStateCallback callback, Handler handler) {
+ mCallback = callback;
+ mHandler = handler;
+ }
+ }
+
private final IVrManager mService;
+ private Map<VrStateCallback, CallbackEntry> mCallbackMap = new ArrayMap<>();
/**
* {@hide}
@@ -26,6 +57,84 @@
}
/**
+ * Registers a callback to be notified of changes to the VR Mode state.
+ *
+ * @param callback The callback to register.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+ public void registerVrStateCallback(VrStateCallback callback, @NonNull Handler handler) {
+ if (callback == null || mCallbackMap.containsKey(callback)) {
+ return;
+ }
+
+ CallbackEntry entry = new CallbackEntry(callback, handler);
+ mCallbackMap.put(callback, entry);
+ try {
+ mService.registerListener(entry.mStateCallback);
+ mService.registerPersistentVrStateListener(entry.mPersistentStateCallback);
+ } catch (RemoteException e) {
+ try {
+ unregisterVrStateCallback(callback);
+ } catch (Exception ignore) {
+ e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Deregisters VR State callbacks.
+ *
+ * @param callback The callback to deregister.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+ public void unregisterVrStateCallback(VrStateCallback callback) {
+ CallbackEntry entry = mCallbackMap.remove(callback);
+ if (entry != null) {
+ try {
+ mService.unregisterListener(entry.mStateCallback);
+ } catch (RemoteException ignore) {
+ // Dont rethrow exceptions from requests to unregister.
+ }
+
+ try {
+ mService.unregisterPersistentVrStateListener(entry.mPersistentStateCallback);
+ } catch (RemoteException ignore) {
+ // Dont rethrow exceptions from requests to unregister.
+ }
+ }
+ }
+
+ /**
+ * Returns the current VrMode state.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_VR_STATE)
+ public boolean getVrModeEnabled() {
+ try {
+ return mService.getVrModeState();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
+ /**
+ * Returns the current VrMode state.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_VR_STATE)
+ public boolean getPersistentVrModeEnabled() {
+ try {
+ return mService.getPersistentVrModeEnabled();
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ return false;
+ }
+
+ /**
* Sets the persistent VR mode state of a device. When a device is in persistent VR mode it will
* remain in VR mode even if the foreground does not specify Vr mode being enabled. Mainly used
* by VR viewers to indicate that a device is placed in a VR viewer.
diff --git a/core/java/android/app/VrStateCallback.java b/core/java/android/app/VrStateCallback.java
new file mode 100644
index 0000000..742faa06
--- /dev/null
+++ b/core/java/android/app/VrStateCallback.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app;
+
+/**
+ * Listens to VR Mode state changes. Use with methods in {@link VrManager}.
+ *
+ * @hide
+ */
+public abstract class VrStateCallback {
+
+ /**
+ * Callback triggered when there is a change to Persistent VR State.
+ *
+ * @param enabled True when VR State is in persistent mode, false otherwise.
+ */
+ public void onPersistentVrStateChanged(boolean enabled) {}
+
+ /**
+ * Callback triggered when there is a change to Vr State.
+ *
+ * @param enabled True when VR State is in VR mode, false otherwise.
+ */
+ public void onVrStateChanged(boolean enabled) {}
+}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 3ade2e4..6fbacaf 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -18,8 +18,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
+import android.graphics.Point;
import android.media.projection.MediaProjection;
import android.os.Handler;
import android.util.SparseArray;
@@ -586,6 +588,20 @@
}
/**
+ * Gets the stable device display size, in pixels.
+ *
+ * This should really only be used for things like server-side filtering of available
+ * applications. Most applications don't need the level of stability guaranteed by this and
+ * should instead query either the size of the display they're currently running on or the
+ * size of the default display.
+ * @hide
+ */
+ @SystemApi
+ public Point getStableDisplaySize() {
+ return mGlobal.getStableDisplaySize();
+ }
+
+ /**
* Listens for changes in available display devices.
*/
public interface DisplayListener {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 0b998e5..a8a4eb6 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Point;
import android.hardware.display.DisplayManager.DisplayListener;
import android.media.projection.IMediaProjection;
import android.media.projection.MediaProjection;
@@ -444,6 +445,17 @@
}
}
+ /**
+ * Gets the stable device display size, in pixels.
+ */
+ public Point getStableDisplaySize() {
+ try {
+ return mDm.getStableDisplaySize();
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
@Override
public void onDisplayEvent(int displayId, int event) {
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index 7ca4dc1..5053884 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -16,6 +16,7 @@
package android.hardware.display;
+import android.graphics.Point;
import android.hardware.display.IDisplayManagerCallback;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.WifiDisplay;
@@ -77,4 +78,7 @@
// No permissions required but must be same Uid as the creator.
void releaseVirtualDisplay(in IVirtualDisplayCallback token);
+
+ // Get a stable metric for the device's display size. No permissions required.
+ Point getStableDisplaySize();
}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 7852125..0627998 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -773,7 +773,7 @@
/**
* O MR1.
*/
- public static final int O_MR1 = CUR_DEVELOPMENT; // STOPSHIP Replace with the real version.
+ public static final int O_MR1 = 27;
}
/** The type of build, like "user" or "eng". */
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 2755e8e..1dcaef4 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -306,7 +306,7 @@
* <pre class="prettyprint">
* private static String getCanonicalDomain(String domain) {
* InternetDomainName idn = InternetDomainName.from(domain);
- * while (!idn.isTopPrivateDomain() && idn != null) {
+ * while (idn != null && !idn.isTopPrivateDomain()) {
* idn = idn.parent();
* }
* return idn == null ? null : idn.toString();
@@ -314,8 +314,9 @@
* </pre>
*
* <p>If the association between the web domain and app package cannot be verified through the steps
- * above, the service can still autofill the app, but it should warn the user about the potential
- * data leakage first, and askfor the user to confirm. For example, the service could:
+ * above, but the service thinks that it is appropriate to fill persisted credentials that are
+ * stored for the web domain, the service should warn the user about the potential data
+ * leakage first, and ask for the user to confirm. For example, the service could:
*
* <ol>
* <li>Create a dataset that requires
@@ -324,7 +325,7 @@
* <li>Include the web domain in the custom presentation for the
* {@link Dataset.Builder#setValue(AutofillId, AutofillValue, android.widget.RemoteViews)
* dataset value}.
- * <li>When the user select that dataset, show a disclaimer dialog explaining that the app is
+ * <li>When the user selects that dataset, show a disclaimer dialog explaining that the app is
* requesting credentials for a web domain, but the service could not verify if the app owns
* that domain. If the user agrees, then the service can unlock the dataset.
* <li>Similarly, when adding a {@link SaveInfo} object for the request, the service should
@@ -333,7 +334,7 @@
*
* <p>This same procedure could also be used when the autofillable data is contained inside an
* {@code IFRAME}, in which case the WebView generates a new autofill context when a node inside
- * the {@code IFRAME} is focused, which the root node containing the {@code IFRAME}'s {@code src}
+ * the {@code IFRAME} is focused, with the root node containing the {@code IFRAME}'s {@code src}
* attribute on {@link android.app.assist.AssistStructure.ViewNode#getWebDomain()}. A typical and
* legitimate use case for this scenario is a financial app that allows the user
* to login on different bank accounts. For example, a financial app {@code my_financial_app} could
diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl
index 9b37a65..c38fab1 100644
--- a/core/java/android/service/vr/IVrManager.aidl
+++ b/core/java/android/service/vr/IVrManager.aidl
@@ -59,6 +59,13 @@
boolean getVrModeState();
/**
+ * Returns the current Persistent VR mode state.
+ *
+ * @return {@code true} if Persistent VR mode is enabled.
+ */
+ boolean getPersistentVrModeEnabled();
+
+ /**
* Sets the persistent VR mode state of a device. When a device is in persistent VR mode it will
* remain in VR mode even if the foreground does not specify VR mode being enabled. Mainly used
* by VR viewers to indicate that a device is placed in a VR viewer.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8a9b14e..ec8ffd7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7450,7 +7450,7 @@
* {@link ViewStructure#setAutofillOptions(CharSequence[])}.
* </ul>
*
- * <p><b>NOTE:</b> the {@code left} and {@code top} values set in
+ * <p><b>Note:</b> The {@code left} and {@code top} values set in
* {@link ViewStructure#setDimens(int, int, int, int, int, int)} must be relative to the next
* {@link ViewGroup#isImportantForAutofill()} predecessor view included in the structure.
*
@@ -7688,6 +7688,10 @@
* {@link AutofillManager#notifyValueChanged(View)} must happen <b>after</b> the value was
* changed to the autofilled value. If not, the view will not be considered autofilled.
*
+ * <p><b>Note:</b> After this method is called, the value returned by
+ * {@link #getAutofillValue()} must be equal to the {@code value} passed to it, otherwise the
+ * view will not be highlighted as autofilled.
+ *
* @param value value to be autofilled.
*/
public void autofill(@SuppressWarnings("unused") AutofillValue value) {
@@ -7711,7 +7715,7 @@
* <b>after</b> the value was changed to the autofilled value. If not, the child will not be
* considered autofilled.
*
- * <p><b>NOTE:</b> to indicate that a virtual view was autofilled,
+ * <p><b>Note:</b> To indicate that a virtual view was autofilled,
* <code>?android:attr/autofilledHighlight</code> should be drawn over it until the data
* changes.
*
@@ -7780,8 +7784,8 @@
/**
* Gets the {@link View}'s current autofill value.
*
- * <p>By default returns {@code null}, but views should override it to properly support the
- * Autofill Framework.
+ * <p>By default returns {@code null}, but subclasses should override it and return an
+ * appropriate value to properly support the Autofill Framework.
*
* @see #onProvideAutofillStructure(ViewStructure, int)
* @see #autofill(AutofillValue)
@@ -7833,7 +7837,7 @@
* be {@link #IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS}.
* </ol>
*
- * <p><b>NOTE:</strong> setting the mode as does {@link #IMPORTANT_FOR_AUTOFILL_NO} or
+ * <p><b>Note:</b> Setting the mode as {@link #IMPORTANT_FOR_AUTOFILL_NO} or
* {@link #IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS} does not guarantee the view (and its
* children) will be always be considered not important; for example, when the user explicitly
* makes an autofill request, all views are considered important. See
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index f978b57..bab0306aa 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -112,6 +112,13 @@
*/
void showGlobalActionsMenu();
+ /**
+ * Set whether the top app currently hides the statusbar.
+ *
+ * @param hidesStatusBar whether it is being hidden
+ */
+ void setTopAppHidesStatusBar(boolean hidesStatusBar);
+
void addQsTile(in ComponentName tile);
void remQsTile(in ComponentName tile);
void clickQsTile(in ComponentName tile);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 86c0b43..89bbec2 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3512,6 +3512,11 @@
<permission android:name="android.permission.ACCESS_VR_MANAGER"
android:protectionLevel="signature" />
+ <!-- Required to access VR-Mode state and state change events via {android.app.VrStateCallback}
+ @hide -->
+ <permission android:name="android.permission.ACCESS_VR_STATE"
+ android:protectionLevel="signature|preinstalled" />
+
<!-- Allows an application to whitelist tasks during lock task mode
@hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.UPDATE_LOCK_TASK_PACKAGES"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ca4b355..172cae9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3044,4 +3044,10 @@
<!-- Allow SystemUI to show the shutdown dialog -->
<bool name="config_showSysuiShutdown">true</bool>
+
+ <!-- The stable device width and height in pixels. If these aren't set to a positive number
+ then the device will use the width and height of the default display the first time it's
+ booted. -->
+ <integer name="config_stableDeviceDisplayWidth">-1</integer>
+ <integer name="config_stableDeviceDisplayHeight">-1</integer>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 04cfe48..4cfc1ac 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3069,4 +3069,7 @@
<java-symbol type="layout" name="shutdown_dialog" />
<java-symbol type="dimen" name="chooser_service_spacing" />
<java-symbol type="bool" name="config_showSysuiShutdown" />
+
+ <java-symbol type="integer" name="config_stableDeviceDisplayWidth" />
+ <java-symbol type="integer" name="config_stableDeviceDisplayHeight" />
</resources>
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 05be088..dff31d4 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -16,12 +16,12 @@
package android.media;
-import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.NotificationManager;
@@ -2364,8 +2364,8 @@
* usecases such as voice memo recording, or speech recognition.
* Use {@link #AUDIOFOCUS_GAIN} for a focus request of unknown duration such
* as the playback of a song or a video.
- * @param flags 0 or a combination of {link #AUDIOFOCUS_FLAG_DELAY_OK}
- * and {@link #AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS}.
+ * @param flags 0 or a combination of {link #AUDIOFOCUS_FLAG_DELAY_OK},
+ * {@link #AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS} and {@link #AUDIOFOCUS_FLAG_LOCK}.
* <br>Use 0 when not using any flags for the request, which behaves like
* {@link #requestAudioFocus(OnAudioFocusChangeListener, int, int)}, where either audio
* focus is granted immediately, or the grant request fails because the system is in a
@@ -2377,6 +2377,7 @@
* @throws IllegalArgumentException
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public int requestAudioFocus(OnAudioFocusChangeListener l,
@NonNull AudioAttributes requestAttributes,
int durationHint,
@@ -2416,6 +2417,10 @@
* @deprecated use {@link #requestAudioFocus(AudioFocusRequest, AudioPolicy)}
*/
@SystemApi
+ @RequiresPermission(anyOf= {
+ android.Manifest.permission.MODIFY_PHONE_STATE,
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING
+ })
public int requestAudioFocus(OnAudioFocusChangeListener l,
@NonNull AudioAttributes requestAttributes,
int durationHint,
@@ -2474,6 +2479,7 @@
* @throws IllegalArgumentException when trying to lock focus without an AudioPolicy
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) {
if (afr == null) {
throw new NullPointerException("Illegal null AudioFocusRequest");
@@ -2571,6 +2577,7 @@
* @throws NullPointerException if the {@link AudioFocusInfo} or {@link AudioPolicy} are null.
*/
@SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public int dispatchAudioFocusChange(@NonNull AudioFocusInfo afi, int focusChange,
@NonNull AudioPolicy ap) {
if (afi == null) {
@@ -2622,6 +2629,8 @@
* @deprecated use {@link #abandonAudioFocusRequest(AudioFocusRequest)}
*/
@SystemApi
+ @SuppressLint("Doclava125") // no permission enforcement, but only "undoes" what would have been
+ // done by a matching requestAudioFocus
public int abandonAudioFocus(OnAudioFocusChangeListener l, AudioAttributes aa) {
int status = AUDIOFOCUS_REQUEST_FAILED;
unregisterAudioFocusRequest(l);
@@ -3833,6 +3842,7 @@
* @hide
*/
@SystemApi
+ @SuppressLint("Doclava125") // FIXME is this still used?
public boolean isHdmiSystemAudioSupported() {
try {
return getService().isHdmiSystemAudioSupported();
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index 3322839..1e35cbc 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -55,9 +55,17 @@
<dimen name="battery_height">14.5dp</dimen>
<dimen name="battery_width">9.5dp</dimen>
+ <dimen name="bt_battery_padding">2dp</dimen>
+
<!-- Margin on the right side of the system icon group on Keyguard. -->
<fraction name="battery_button_height_fraction">10.5%</fraction>
+ <!-- Ratio between height of button part and height of total -->
+ <fraction name="bt_battery_button_height_fraction">7.5%</fraction>
+
+ <!-- Ratio between width and height -->
+ <fraction name="bt_battery_ratio_fraction">45%</fraction>
+
<!-- Fraction value to smooth the edges of the battery icon. The path will be inset by this
fraction of a pixel.-->
<fraction name="battery_subpixel_smoothing_left">0%</fraction>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 7b51725..bd884a3 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -484,6 +484,8 @@
<string name="mobile_data_always_on">Mobile data always active</string>
<!-- Setting Checkbox title whether to enable hardware acceleration for tethering. [CHAR LIMIT=80] -->
<string name="tethering_hardware_offload">Tethering hardware acceleration</string>
+ <!-- Setting Checkbox title for showing Bluetooth devices without names -->
+ <string name="bluetooth_show_devices_without_names">Show Bluetooth devices without names</string>
<!-- Setting Checkbox title for disabling Bluetooth absolute volume -->
<string name="bluetooth_disable_absolute_volume">Disable absolute volume</string>
<!-- Setting Checkbox title for enabling Bluetooth inband ringing -->
@@ -568,12 +570,13 @@
<string name="verify_apps_over_usb_title">Verify apps over USB</string>
<!-- Summary of checkbox setting to perform package verification on apps installed over USB/ADT/ADB [CHAR LIMIT=NONE] -->
<string name="verify_apps_over_usb_summary">Check apps installed via ADB/ADT for harmful behavior.</string>
+ <!-- Summary of checkbox for showing Bluetooth devices without names -->
+ <string name="bluetooth_show_devices_without_names_summary">Bluetooth devices without names (MAC addresses only) will be displayed</string>
<!-- Summary of checkbox for disabling Bluetooth absolute volume -->
<string name="bluetooth_disable_absolute_volume_summary">Disables the Bluetooth absolute volume feature in case of volume issues with remote devices such as unacceptably loud volume or lack of control.</string>
<!-- Summary of checkbox for enabling Bluetooth inband ringing -->
<string name="bluetooth_enable_inband_ringing_summary">Allow ringtones on the phone to be played on Bluetooth headsets</string>
-
<!-- Title of checkbox setting that enables the terminal app. [CHAR LIMIT=32] -->
<string name="enable_terminal_title">Local terminal</string>
<!-- Summary of checkbox setting that enables the terminal app. [CHAR LIMIT=64] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
index 426dc7c..d1f91d9 100755
--- a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
@@ -50,6 +50,7 @@
protected final Paint mTextPaint;
protected final Paint mBoltPaint;
protected final Paint mPlusPaint;
+ protected float mButtonHeightFraction;
private int mLevel = -1;
private boolean mCharging;
@@ -66,7 +67,6 @@
private final int mIntrinsicWidth;
private final int mIntrinsicHeight;
- private float mButtonHeightFraction;
private float mSubpixelSmoothingLeft;
private float mSubpixelSmoothingRight;
private float mTextHeight, mWarningTextHeight;
@@ -298,7 +298,7 @@
float drawFrac = (float) level / 100f;
final int height = mHeight;
- final int width = (int) (ASPECT_RATIO * mHeight);
+ final int width = (int) (getAspectRatio() * mHeight);
final int px = (mWidth - width) / 2;
final int buttonHeight = Math.round(height * mButtonHeightFraction);
@@ -329,7 +329,7 @@
// define the battery shape
mShapePath.reset();
- final float radius = RADIUS_RATIO * (mFrame.height() + buttonHeight);
+ final float radius = getRadiusRatio() * (mFrame.height() + buttonHeight);
mShapePath.setFillType(FillType.WINDING);
mShapePath.addRoundRect(mFrame, radius, radius, Direction.CW);
mShapePath.addRect(mButtonFrame, Direction.CW);
@@ -469,4 +469,12 @@
public int getCriticalLevel() {
return mCriticalLevel;
}
+
+ protected float getAspectRatio() {
+ return ASPECT_RATIO;
+ }
+
+ protected float getRadiusRatio() {
+ return RADIUS_RATIO;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
new file mode 100644
index 0000000..61790b9
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.graph;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.support.annotation.VisibleForTesting;
+import android.view.Gravity;
+import android.view.View;
+
+import com.android.settingslib.R;
+import com.android.settingslib.Utils;
+
+/**
+ * LayerDrawable contains the bluetooth device icon and battery gauge icon
+ */
+public class BluetoothDeviceLayerDrawable extends LayerDrawable {
+
+ private BluetoothDeviceLayerDrawableState mState;
+
+ private BluetoothDeviceLayerDrawable(@NonNull Drawable[] layers) {
+ super(layers);
+ }
+
+ /**
+ * Create the {@link LayerDrawable} that contains bluetooth device icon and battery icon.
+ * This is a vertical layout drawable while bluetooth icon at top and battery icon at bottom.
+ *
+ * @param context used to get the spec for icon
+ * @param resId represents the bluetooth device drawable
+ * @param batteryLevel the battery level for bluetooth device
+ */
+ public static BluetoothDeviceLayerDrawable createLayerDrawable(Context context, int resId,
+ int batteryLevel) {
+ final Drawable deviceDrawable = context.getDrawable(resId);
+
+ final BatteryMeterDrawable batteryDrawable = new BatteryMeterDrawable(context,
+ R.color.meter_background_color, batteryLevel);
+ final int pad = context.getResources()
+ .getDimensionPixelSize(R.dimen.bt_battery_padding);
+ batteryDrawable.setPadding(0, pad, 0, pad);
+
+ final BluetoothDeviceLayerDrawable drawable = new BluetoothDeviceLayerDrawable(
+ new Drawable[]{deviceDrawable,
+ rotateDrawable(context.getResources(), batteryDrawable)});
+ // Set the bluetooth icon at the top
+ drawable.setLayerGravity(0 /* index of deviceDrawable */, Gravity.TOP);
+ // Set battery icon right below the bluetooth icon
+ drawable.setLayerInset(1 /* index of batteryDrawable */, 0,
+ deviceDrawable.getIntrinsicHeight(), 0, 0);
+
+ drawable.setConstantState(context, resId, batteryLevel);
+
+ return drawable;
+ }
+
+ /**
+ * Rotate the {@code drawable} by 90 degree clockwise and return rotated {@link Drawable}
+ */
+ private static Drawable rotateDrawable(Resources res, Drawable drawable) {
+ // Get the bitmap from drawable
+ final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+
+ // Create rotate matrix
+ final Matrix matrix = new Matrix();
+ matrix.postRotate(
+ res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR
+ ? 90 : 270);
+
+ // Create new bitmap with rotate matrix
+ final Bitmap rotateBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
+ bitmap.getHeight(), matrix, true);
+ bitmap.recycle();
+
+ return new BitmapDrawable(res, rotateBitmap);
+ }
+
+ public void setConstantState(Context context, int resId, int batteryLevel) {
+ mState = new BluetoothDeviceLayerDrawableState(context, resId, batteryLevel);
+ }
+
+ @Override
+ public ConstantState getConstantState() {
+ return mState;
+ }
+
+ /**
+ * Battery gauge icon with new spec.
+ */
+ @VisibleForTesting
+ static class BatteryMeterDrawable extends BatteryMeterDrawableBase {
+ private final float mAspectRatio;
+
+ public BatteryMeterDrawable(Context context, int frameColor, int batteryLevel) {
+ super(context, frameColor);
+ final Resources resources = context.getResources();
+ mButtonHeightFraction = resources.getFraction(
+ R.fraction.bt_battery_button_height_fraction, 1, 1);
+ mAspectRatio = resources.getFraction(R.fraction.bt_battery_ratio_fraction, 1, 1);
+
+ final int tintColor = Utils.getColorAttr(context, android.R.attr.colorControlNormal);
+ setColorFilter(new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN));
+ setBatteryLevel(batteryLevel);
+ }
+
+ @Override
+ protected float getAspectRatio() {
+ return mAspectRatio;
+ }
+
+ @Override
+ protected float getRadiusRatio() {
+ // Remove the round edge
+ return 0;
+ }
+ }
+
+ /**
+ * {@link ConstantState} to restore the {@link BluetoothDeviceLayerDrawable}
+ */
+ private static class BluetoothDeviceLayerDrawableState extends ConstantState {
+ Context context;
+ int resId;
+ int batteryLevel;
+
+ public BluetoothDeviceLayerDrawableState(Context context, int resId,
+ int batteryLevel) {
+ this.context = context;
+ this.resId = resId;
+ this.batteryLevel = batteryLevel;
+ }
+
+ @Override
+ public Drawable newDrawable() {
+ return createLayerDrawable(context, resId, batteryLevel);
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return 0;
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java
new file mode 100644
index 0000000..89855be
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawableTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.graph;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.VectorDrawable;
+
+import com.android.settingslib.R;
+import com.android.settingslib.SettingLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.testutils.shadow.SettingsLibShadowResources;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+ shadows = SettingsLibShadowResources.class)
+public class BluetoothDeviceLayerDrawableTest {
+ private static final int RES_ID = R.drawable.ic_bt_cellphone;
+ private static final int BATTERY_LEVEL = 15;
+ private static final float TOLERANCE = 0.001f;
+
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = RuntimeEnvironment.application;
+ }
+
+ @Test
+ public void testCreateLayerDrawable_configCorrect() {
+ BluetoothDeviceLayerDrawable drawable = BluetoothDeviceLayerDrawable.createLayerDrawable(
+ mContext, RES_ID, BATTERY_LEVEL);
+
+ assertThat(drawable.getDrawable(0)).isInstanceOf(VectorDrawable.class);
+ assertThat(drawable.getDrawable(1)).isInstanceOf(BitmapDrawable.class);
+ assertThat(drawable.getLayerInsetTop(1)).isEqualTo(
+ drawable.getDrawable(0).getIntrinsicHeight());
+ }
+
+ @Test
+ public void testBatteryMeterDrawable_configCorrect() {
+ BluetoothDeviceLayerDrawable.BatteryMeterDrawable batteryDrawable =
+ new BluetoothDeviceLayerDrawable.BatteryMeterDrawable(mContext,
+ R.color.meter_background_color, BATTERY_LEVEL);
+
+ assertThat(batteryDrawable.getAspectRatio()).isWithin(TOLERANCE).of(0.45f);
+ assertThat(batteryDrawable.getRadiusRatio()).isWithin(TOLERANCE).of(0f);
+ assertThat(batteryDrawable.getBatteryLevel()).isEqualTo(BATTERY_LEVEL);
+ }
+
+ @Test
+ public void testConstantState_returnTwinBluetoothLayerDrawable() {
+ BluetoothDeviceLayerDrawable drawable = BluetoothDeviceLayerDrawable.createLayerDrawable(
+ mContext, RES_ID, BATTERY_LEVEL);
+
+ BluetoothDeviceLayerDrawable twinDrawable =
+ (BluetoothDeviceLayerDrawable) drawable.getConstantState().newDrawable();
+
+ assertThat(twinDrawable.getDrawable(0)).isEqualTo(drawable.getDrawable(0));
+ assertThat(twinDrawable.getDrawable(1)).isEqualTo(drawable.getDrawable(1));
+ assertThat(twinDrawable.getLayerInsetTop(1)).isEqualTo(
+ drawable.getLayerInsetTop(1));
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/SettingsLibShadowResources.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/SettingsLibShadowResources.java
new file mode 100644
index 0000000..a376dcd
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/shadow/SettingsLibShadowResources.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.testutils.shadow;
+
+import static org.robolectric.internal.Shadow.directlyOn;
+
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.support.annotation.ArrayRes;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.RealObject;
+import org.robolectric.shadows.ShadowResources;
+
+/**
+ * Shadow Resources to handle resource references that Robolectric shadows cannot
+ * handle because they are too new or private.
+ */
+@Implements(Resources.class)
+public class SettingsLibShadowResources extends ShadowResources {
+
+ @RealObject
+ public Resources realResources;
+
+ @Implementation
+ public int[] getIntArray(@ArrayRes int id) throws NotFoundException {
+ // The Robolectric has resource mismatch for these values, so we need to stub it here
+ if (id == com.android.settingslib.R.array.batterymeter_bolt_points
+ || id == com.android.settingslib.R.array.batterymeter_plus_points) {
+ return new int[2];
+ }
+ return directlyOn(realResources, Resources.class).getIntArray(id);
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
index 1285ed8..56a3ee3 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/phone/NavBarButtonProvider.java
@@ -27,7 +27,7 @@
public static final String ACTION = "com.android.systemui.action.PLUGIN_NAV_BUTTON";
- public static final int VERSION = 3;
+ public static final int VERSION = 2;
/**
* Returns a view in the nav bar. If the id is set "back", "home", "recent_apps", "menu",
@@ -46,6 +46,9 @@
void setVertical(boolean vertical);
+ default void setCarMode(boolean carMode) {
+ }
+
void setDarkIntensity(float intensity);
}
}
diff --git a/packages/SystemUI/res/layout/global_actions_item.xml b/packages/SystemUI/res/layout/global_actions_item.xml
index e3a488c..0d735e7 100644
--- a/packages/SystemUI/res/layout/global_actions_item.xml
+++ b/packages/SystemUI/res/layout/global_actions_item.xml
@@ -25,8 +25,8 @@
android:minHeight="92dp"
android:gravity="center"
android:orientation="vertical"
- android:paddingEnd="8dip"
- android:paddingStart="8dip">
+ android:paddingEnd="0dip"
+ android:paddingStart="0dip">
<ImageView
android:id="@*android:id/icon"
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
index 4c7b48d..7225ba9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinView.java
@@ -60,9 +60,19 @@
KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
@Override
public void onSimStateChanged(int subId, int slotId, State simState) {
- if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
- resetState();
- };
+ if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
+ switch(simState) {
+ // If the SIM is removed, then we must remove the keyguard. It will be put up
+ // again when the PUK locked SIM is re-entered.
+ case ABSENT: {
+ KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked(mSubId);
+ mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+ break;
+ }
+ default:
+ resetState();
+ }
+ }
};
public KeyguardSimPinView(Context context) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index d8163ba..171cf23 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -62,9 +62,23 @@
KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() {
@Override
public void onSimStateChanged(int subId, int slotId, State simState) {
- if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
- resetState();
- };
+ if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")");
+ switch(simState) {
+ // If the SIM is removed, then we must remove the keyguard. It will be put up
+ // again when the PUK locked SIM is re-entered.
+ case ABSENT:
+ // intentional fall-through
+ // If the SIM is unlocked via a key sequence through the emergency dialer, it will
+ // move into the READY state and the PUK lock keyguard should be removed.
+ case READY: {
+ KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked(mSubId);
+ mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+ break;
+ }
+ default:
+ resetState();
+ }
+ }
};
public KeyguardSimPukView(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 8c1b736..4b37715 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -83,6 +83,7 @@
private boolean mMenuRowIntercepting;
private boolean mLongPressSent;
+ private LongPressListener mLongPressListener;
private Runnable mWatchLongPress;
private final long mLongPressTimeout;
@@ -114,6 +115,10 @@
mFlingAnimationUtils = new FlingAnimationUtils(context, getMaxEscapeAnimDuration() / 1000f);
}
+ public void setLongPressListener(LongPressListener listener) {
+ mLongPressListener = listener;
+ }
+
public void setDensityScale(float densityScale) {
mDensityScale = densityScale;
}
@@ -252,7 +257,7 @@
}
}
- public void cancelLongPress() {
+ public void removeLongPressCallback() {
if (mWatchLongPress != null) {
mHandler.removeCallbacks(mWatchLongPress);
mWatchLongPress = null;
@@ -276,14 +281,6 @@
mVelocityTracker.clear();
mCurrView = mCallback.getChildAtPosition(ev);
- // The SwipeHelper sends its own long-press, don't let the view send a dupe.
- // Queue up a cancelLongPress on the view a few ms after we see a down event.
- mHandler.post(() -> {
- if (mCurrView != null) {
- mCurrView.cancelLongPress();
- }
- });
-
if (mCurrView != null) {
onDownUpdate(mCurrView, ev);
mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
@@ -291,26 +288,33 @@
mInitialTouchPos = getPos(ev);
mPerpendicularInitialTouchPos = getPerpendicularPos(ev);
mTranslation = getTranslation(mCurrView);
- if (mWatchLongPress == null) {
- mWatchLongPress = new Runnable() {
- @Override
- public void run() {
- if (mCurrView != null && !mLongPressSent) {
- mLongPressSent = true;
- mCurrView.getLocationOnScreen(mTmpPos);
- final int x = (int) ev.getRawX() - mTmpPos[0];
- final int y = (int) ev.getRawY() - mTmpPos[1];
- if (mCurrView instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow currRow =
- (ExpandableNotificationRow) mCurrView;
- currRow.setLongPressPosition(x, y);
- currRow.performLongClick(x, y);
+ if (mLongPressListener != null) {
+ if (mWatchLongPress == null) {
+ mWatchLongPress = new Runnable() {
+ @Override
+ public void run() {
+ if (mCurrView != null && !mLongPressSent) {
+ mLongPressSent = true;
+ mCurrView.sendAccessibilityEvent(
+ AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+ mCurrView.getLocationOnScreen(mTmpPos);
+ final int x = (int) ev.getRawX() - mTmpPos[0];
+ final int y = (int) ev.getRawY() - mTmpPos[1];
+ MenuItem menuItem = null;
+ if (mCurrView instanceof ExpandableNotificationRow) {
+ menuItem = ((ExpandableNotificationRow) mCurrView)
+ .getProvider().getLongpressMenuItem(mContext);
+ }
+ if (menuItem != null) {
+ mLongPressListener.onLongPress(mCurrView, x, y,
+ menuItem);
+ }
}
}
- }
- };
+ };
+ }
+ mHandler.postDelayed(mWatchLongPress, mLongPressTimeout);
}
- mHandler.postDelayed(mWatchLongPress, mLongPressTimeout);
}
break;
@@ -327,7 +331,7 @@
mDragging = true;
mInitialTouchPos = getPos(ev);
mTranslation = getTranslation(mCurrView);
- cancelLongPress();
+ removeLongPressCallback();
}
}
break;
@@ -339,7 +343,7 @@
mCurrView = null;
mLongPressSent = false;
mMenuRowIntercepting = false;
- cancelLongPress();
+ removeLongPressCallback();
if (captured) return true;
break;
}
@@ -582,7 +586,7 @@
// We are not doing anything, make sure the long press callback
// is not still ticking like a bomb waiting to go off.
- cancelLongPress();
+ removeLongPressCallback();
return false;
}
}
@@ -730,4 +734,15 @@
*/
float getFalsingThresholdFactor();
}
+
+ /**
+ * Equivalent to View.OnLongClickListener with coordinates
+ */
+ public interface LongPressListener {
+ /**
+ * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
+ * @return whether the longpress was handled
+ */
+ boolean onLongPress(View v, int x, int y, MenuItem item);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index bbf9eb1..6349275 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -81,6 +81,7 @@
private static final int MSG_SHOW_GLOBAL_ACTIONS = 34 << MSG_SHIFT;
private static final int MSG_TOGGLE_PANEL = 35 << MSG_SHIFT;
private static final int MSG_SHOW_SHUTDOWN_UI = 36 << MSG_SHIFT;
+ private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR = 37 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -132,6 +133,7 @@
default void startAssist(Bundle args) { }
default void onCameraLaunchGestureDetected(int source) { }
default void showPictureInPictureMenu() { }
+ default void setTopAppHidesStatusBar(boolean topAppHidesStatusBar) { }
default void addQsTile(ComponentName tile) { }
default void remQsTile(ComponentName tile) { }
@@ -441,6 +443,13 @@
}
@Override
+ public void setTopAppHidesStatusBar(boolean hidesStatusBar) {
+ mHandler.removeMessages(MSG_SET_TOP_APP_HIDES_STATUS_BAR);
+ mHandler.obtainMessage(MSG_SET_TOP_APP_HIDES_STATUS_BAR, hidesStatusBar ? 1 : 0, 0)
+ .sendToTarget();
+ }
+
+ @Override
public void showShutdownUi(boolean isReboot, String reason) {
synchronized (mLock) {
mHandler.removeMessages(MSG_SHOW_SHUTDOWN_UI);
@@ -640,6 +649,11 @@
mCallbacks.get(i).handleShowShutdownUi(msg.arg1 != 0, (String) msg.obj);
}
break;
+ case MSG_SET_TOP_APP_HIDES_STATUS_BAR:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).setTopAppHidesStatusBar(msg.arg1 != 0);
+ }
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index b43626f..7bc1a39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -172,14 +172,6 @@
private boolean mShowNoBackground;
private ExpandableNotificationRow mNotificationParent;
private OnExpandClickListener mOnExpandClickListener;
-
- // Listener will be called when receiving a long click event.
- // Use #setLongPressPosition to optionally assign positional data with the long press.
- private LongPressListener mLongPressListener;
- private boolean mLongPressPositionSet = false;
- private int mLongPressX = 0;
- private int mLongPressY = 0;
-
private boolean mGroupExpansionChanging;
/**
@@ -781,16 +773,6 @@
mOnExpandClickListener = onExpandClickListener;
}
- public void setLongPressListener(LongPressListener longPressListener) {
- mLongPressListener = longPressListener;
- }
-
- public void setLongPressPosition(int x, int y) {
- mLongPressPositionSet = true;
- mLongPressX = x;
- mLongPressY = y;
- }
-
@Override
public void setOnClickListener(@Nullable OnClickListener l) {
super.setOnClickListener(l);
@@ -1339,25 +1321,6 @@
mTranslateableViews.remove(mChildrenContainerStub);
mTranslateableViews.remove(mGutsStub);
}
-
- setOnLongClickListener((View v) -> {
- createMenu();
- MenuItem menuItem = getProvider().getLongpressMenuItem(mContext);
- if (mLongPressListener != null && menuItem != null) {
- int x, y;
- if (mLongPressPositionSet) {
- x = mLongPressX;
- y = mLongPressY;
- } else {
- // No position assigned - use the center of the View
- x = getWidth() / 2;
- y = getHeight() / 2;
- }
- mLongPressPositionSet = false;
- return mLongPressListener.onLongPress(this, x, y, menuItem);
- }
- return false;
- });
}
public void resetTranslation() {
@@ -2341,15 +2304,4 @@
protected void setChildrenContainer(NotificationChildrenContainer childrenContainer) {
mChildrenContainer = childrenContainer;
}
-
- /**
- * Equivalent to View.OnLongClickListener with coordinates
- */
- public interface LongPressListener {
- /**
- * Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
- * @return whether the longpress was handled
- */
- boolean onLongPress(View v, int x, int y, MenuItem item);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 2c50736..680f693 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -44,7 +44,6 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
-import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
@@ -248,12 +247,11 @@
}
/**
- * Returns the
- * {@link com.android.systemui.statusbar.ExpandableNotificationRow.LongPressListener} that will
- * be triggered when a notification card is long-pressed.
+ * Returns the {@link com.android.systemui.SwipeHelper.LongPressListener} that will be
+ * triggered when a notification card is long-pressed.
*/
@Override
- protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
+ protected SwipeHelper.LongPressListener getNotificationLongClicker() {
// For the automative use case, we do not want to the user to be able to interact with
// a notification other than a regular click. As a result, just return null for the
// long click listener.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 4339ade..5c04058 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -179,7 +179,13 @@
}
private boolean shouldHideNotificationIcons() {
- return !mStatusBar.isClosed() && mStatusBarComponent.hideStatusBarIconsWhenExpanded();
+ if (!mStatusBar.isClosed() && mStatusBarComponent.hideStatusBarIconsWhenExpanded()) {
+ return true;
+ }
+ if (mStatusBarComponent.hideStatusBarIconsForBouncer()) {
+ return true;
+ }
+ return false;
}
public void hideSystemIconArea(boolean animate) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 6cfa838..f058862 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -72,7 +72,6 @@
import com.android.systemui.plugins.IntentButtonProvider.IntentButton;
import com.android.systemui.plugins.IntentButtonProvider.IntentButton.IconState;
import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardAffordanceView;
@@ -82,10 +81,8 @@
import com.android.systemui.statusbar.policy.ExtensionController.Extension;
import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.PreviewInflater;
-import com.android.systemui.tuner.LockscreenFragment;
import com.android.systemui.tuner.LockscreenFragment.LockButtonFactory;
import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerService.Tunable;
/**
* Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
@@ -510,6 +507,7 @@
// force the crossfade animation if an orientation change
// happens to occur during the launch.
ActivityOptions o = ActivityOptions.makeBasic();
+ o.setDisallowEnterPictureInPictureWhileLaunching(true);
o.setRotationAnimationHint(
WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
try {
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 9453035..078e818 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -32,6 +32,7 @@
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
+import android.os.PowerManager;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.MathUtils;
@@ -108,6 +109,7 @@
return object.mDarkAmount;
}
};
+ private final PowerManager mPowerManager;
private KeyguardAffordanceHelper mAffordanceHelper;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
@@ -242,6 +244,7 @@
super(context, attrs);
setWillNotDraw(!DEBUG);
mFalsingManager = FalsingManager.getInstance(context);
+ mPowerManager = context.getSystemService(PowerManager.class);
}
public void setStatusBar(StatusBar bar) {
@@ -700,7 +703,7 @@
mInitialHeightOnTouch = mQsExpansionHeight;
mQsTracking = true;
mIntercepting = false;
- mNotificationStackScroller.cancelLongPress();
+ mNotificationStackScroller.removeLongPressCallback();
}
break;
case MotionEvent.ACTION_POINTER_UP:
@@ -736,7 +739,7 @@
mInitialTouchY = y;
mInitialTouchX = x;
mIntercepting = false;
- mNotificationStackScroller.cancelLongPress();
+ mNotificationStackScroller.removeLongPressCallback();
return true;
}
break;
@@ -1974,6 +1977,11 @@
@Override
protected void startUnlockHintAnimation() {
+ if (mPowerManager.isPowerSaveMode()) {
+ onUnlockHintStarted();
+ onUnlockHintFinished();
+ return;
+ }
super.startUnlockHintAnimation();
startHighlightIconAnimation(getCenterIcon());
}
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 b2c97b1..75db6e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -104,7 +104,6 @@
import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
-import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.RankingMap;
import android.service.notification.StatusBarNotification;
import android.service.vr.IVrManager;
@@ -143,7 +142,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
-import com.android.internal.graphics.ColorUtils;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
@@ -167,6 +165,7 @@
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
+import com.android.systemui.SwipeHelper;
import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.UiOffloadThread;
@@ -639,6 +638,12 @@
// Fingerprint (as computed by getLoggingFingerprint() of the last logged state.
private int mLastLoggedStateFingerprint;
+ private boolean mTopHidesStatusBar;
+ private boolean mStatusBarWindowHidden;
+ private boolean mHideIconsForBouncer;
+ private boolean mIsOccluded;
+ private boolean mWereIconsJustHidden;
+ private boolean mBouncerWasShowingWhenHidden;
public boolean isStartedGoingToSleep() {
return mStartedGoingToSleep;
@@ -2674,7 +2679,8 @@
@Override
public void startActivity(Intent intent, boolean dismissShade, Callback callback) {
- startActivityDismissingKeyguard(intent, false, dismissShade, callback);
+ startActivityDismissingKeyguard(intent, false, dismissShade,
+ false /* disallowEnterPictureInPictureWhileLaunching */, callback);
}
public void setQsExpanded(boolean expanded) {
@@ -2825,6 +2831,7 @@
public void setPanelExpanded(boolean isExpanded) {
mPanelExpanded = isExpanded;
+ updateHideIconsForBouncer(false /* animate */);
mStatusBarWindowManager.setPanelExpanded(isExpanded);
mVisualStabilityManager.setPanelExpanded(isExpanded);
if (isExpanded && getBarState() != StatusBarState.KEYGUARD) {
@@ -2890,6 +2897,40 @@
return mAmbientIndicationContainer;
}
+ public void setOccluded(boolean occluded) {
+ mIsOccluded = occluded;
+ updateHideIconsForBouncer(false /* animate */);
+ }
+
+ public boolean hideStatusBarIconsForBouncer() {
+ return mHideIconsForBouncer || mWereIconsJustHidden;
+ }
+
+ /**
+ * @param animate should the change of the icons be animated.
+ */
+ private void updateHideIconsForBouncer(boolean animate) {
+ boolean shouldHideIconsForBouncer = !mPanelExpanded && mTopHidesStatusBar && mIsOccluded
+ && (mBouncerShowing || mStatusBarWindowHidden);
+ if (mHideIconsForBouncer != shouldHideIconsForBouncer) {
+ mHideIconsForBouncer = shouldHideIconsForBouncer;
+ if (!shouldHideIconsForBouncer && mBouncerWasShowingWhenHidden) {
+ // We're delaying the showing, since most of the time the fullscreen app will
+ // hide the icons again and we don't want them to fade in and out immediately again.
+ mWereIconsJustHidden = true;
+ mHandler.postDelayed(() -> {
+ mWereIconsJustHidden = false;
+ recomputeDisableFlags(true);
+ }, 500);
+ } else {
+ recomputeDisableFlags(animate);
+ }
+ }
+ if (shouldHideIconsForBouncer) {
+ mBouncerWasShowingWhenHidden = mBouncerShowing;
+ }
+ }
+
/**
* All changes to the status bar and notifications funnel through here and are batched.
*/
@@ -3216,6 +3257,10 @@
mStatusBarView.collapsePanel(false /* animate */, false /* delayed */,
1.0f /* speedUpFactor */);
}
+ if (mStatusBarView != null) {
+ mStatusBarWindowHidden = state == WINDOW_STATE_HIDDEN;
+ updateHideIconsForBouncer(false /* animate */);
+ }
}
}
@@ -3628,11 +3673,13 @@
public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
boolean dismissShade) {
- startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, null /* callback */);
+ startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade,
+ false /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */);
}
public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
- final boolean dismissShade, final Callback callback) {
+ final boolean dismissShade, final boolean disallowEnterPictureInPictureWhileLaunching,
+ final Callback callback) {
if (onlyProvisioned && !isDeviceProvisioned()) return;
final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
@@ -3645,6 +3692,8 @@
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
int result = ActivityManager.START_CANCELED;
ActivityOptions options = new ActivityOptions(getActivityOptions());
+ options.setDisallowEnterPictureInPictureWhileLaunching(
+ disallowEnterPictureInPictureWhileLaunching);
if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) {
// Normally an activity will set it's requested rotation
// animation on its window. However when launching an activity
@@ -4939,7 +4988,7 @@
@Override
public void onTouchSlopExceeded() {
- mStackScroller.cancelLongPress();
+ mStackScroller.removeLongPressCallback();
mStackScroller.checkSnoozeLeavebehind();
}
@@ -5162,6 +5211,7 @@
public void setBouncerShowing(boolean bouncerShowing) {
mBouncerShowing = bouncerShowing;
if (mStatusBarView != null) mStatusBarView.setBouncerShowing(bouncerShowing);
+ updateHideIconsForBouncer(true /* animate */);
recomputeDisableFlags(true /* animate */);
}
@@ -5345,8 +5395,9 @@
}
vibrateForCameraGesture();
if (!mStatusBarKeyguardViewManager.isShowing()) {
- startActivity(KeyguardBottomAreaView.INSECURE_CAMERA_INTENT,
- true /* dismissShade */);
+ startActivityDismissingKeyguard(KeyguardBottomAreaView.INSECURE_CAMERA_INTENT,
+ false /* onlyProvisioned */, true /* dismissShade */,
+ true /* disallowEnterPictureInPictureWhileLaunching */, null /* callback */);
} else {
if (!mDeviceInteractive) {
// Avoid flickering of the scrim when we instant launch the camera and the bouncer
@@ -5580,7 +5631,7 @@
@Override
public void onDoubleTap(float screenX, float screenY) {
- if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
+ if (screenX > 0 && screenY > 0 && mAmbientIndicationContainer != null
&& mAmbientIndicationContainer.getVisibility() == View.VISIBLE) {
mAmbientIndicationContainer.getLocationOnScreen(mTmpInt2);
float viewX = screenX - mTmpInt2[0];
@@ -6426,15 +6477,14 @@
true /* removeControls */, x, y, true /* resetMenu */);
}
- protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
- return new ExpandableNotificationRow.LongPressListener() {
+ protected SwipeHelper.LongPressListener getNotificationLongClicker() {
+ return new SwipeHelper.LongPressListener() {
@Override
public boolean onLongPress(View v, final int x, final int y,
MenuItem item) {
if (!(v instanceof ExpandableNotificationRow)) {
return false;
}
-
if (v.getWindowToken() == null) {
Log.e(TAG, "Trying to show notification guts, but not attached to window");
return false;
@@ -6449,7 +6499,7 @@
closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
true /* removeControls */, -1 /* x */, -1 /* y */,
true /* resetMenu */);
- return true;
+ return false;
}
bindGuts(row, item);
NotificationGuts guts = row.getGuts();
@@ -6568,6 +6618,18 @@
mHandler.obtainMessage(msg, deviceId, 0).sendToTarget();
}
+ @Override
+ public void setTopAppHidesStatusBar(boolean topAppHidesStatusBar) {
+ mTopHidesStatusBar = topAppHidesStatusBar;
+ if (!topAppHidesStatusBar && mWereIconsJustHidden) {
+ // Immediately update the icon hidden state, since that should only apply if we're
+ // staying fullscreen.
+ mWereIconsJustHidden = false;
+ recomputeDisableFlags(true);
+ }
+ updateHideIconsForBouncer(true /* animate */);
+ }
+
protected void sendCloseSystemWindows(String reason) {
try {
ActivityManager.getService().closeSystemDialogs(reason);
@@ -6735,7 +6797,6 @@
row.setOnExpandClickListener(this);
row.setRemoteViewClickHandler(mOnClickHandler);
row.setInflationCallback(this);
- row.setLongPressListener(getNotificationLongClicker());
// Get the app name.
// Note that Notification.Builder#bindHeaderAppName has similar logic
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index b4b859c..bbce751 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -302,6 +302,7 @@
}
public void setOccluded(boolean occluded, boolean animate) {
+ mStatusBar.setOccluded(occluded);
if (occluded && !mOccluded && mShowing) {
if (mStatusBar.isInLaunchTransition()) {
mOccluded = 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 dcf7439..4e592db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -40,6 +40,7 @@
import android.os.Handler;
import android.service.notification.StatusBarNotification;
import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.Log;
@@ -241,7 +242,7 @@
* motion.
*/
private int mMaxScrollAfterExpand;
- private ExpandableNotificationRow.LongPressListener mLongPressListener;
+ private SwipeHelper.LongPressListener mLongPressListener;
private NotificationMenuRowPlugin mCurrMenuRow;
private View mTranslatingParentView;
@@ -411,6 +412,7 @@
mExpandHelper.setEventSource(this);
mExpandHelper.setScrollAdapter(this);
mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext());
+ mSwipeHelper.setLongPressListener(mLongPressListener);
mStackScrollAlgorithm = createStackScrollAlgorithm(context);
initView(context);
mFalsingManager = FalsingManager.getInstance(context);
@@ -884,7 +886,8 @@
return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
}
- public void setLongPressListener(ExpandableNotificationRow.LongPressListener listener) {
+ public void setLongPressListener(SwipeHelper.LongPressListener listener) {
+ mSwipeHelper.setLongPressListener(listener);
mLongPressListener = listener;
}
@@ -1160,7 +1163,7 @@
if (v instanceof ExpandableNotificationRow) {
((ExpandableNotificationRow) v).setUserLocked(userLocked);
}
- cancelLongPress();
+ removeLongPressCallback();
requestDisallowInterceptTouchEvent(true);
}
@@ -2566,7 +2569,7 @@
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
super.requestDisallowInterceptTouchEvent(disallowIntercept);
if (disallowIntercept) {
- cancelLongPress();
+ mSwipeHelper.removeLongPressCallback();
}
}
@@ -3287,7 +3290,7 @@
mIsBeingDragged = isDragged;
if (isDragged) {
requestDisallowInterceptTouchEvent(true);
- cancelLongPress();
+ removeLongPressCallback();
}
}
@@ -3295,7 +3298,7 @@
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (!hasWindowFocus) {
- cancelLongPress();
+ removeLongPressCallback();
}
}
@@ -3309,7 +3312,7 @@
@Override
public void requestDisallowLongPress() {
- cancelLongPress();
+ removeLongPressCallback();
}
@Override
@@ -3317,8 +3320,8 @@
mDisallowDismissInThisMotion = true;
}
- public void cancelLongPress() {
- mSwipeHelper.cancelLongPress();
+ public void removeLongPressCallback() {
+ mSwipeHelper.removeLongPressCallback();
}
@Override
@@ -3543,6 +3546,7 @@
* See {@link AmbientState#setDimmed}.
*/
public void setDimmed(boolean dimmed, boolean animate) {
+ dimmed &= onKeyguard();
mAmbientState.setDimmed(dimmed);
if (animate && mAnimationsEnabled) {
mDimmedNeedsAnimation = true;
@@ -3554,6 +3558,11 @@
requestChildrenUpdate();
}
+ @VisibleForTesting
+ boolean isDimmed() {
+ return mAmbientState.isDimmed();
+ }
+
private void setDimAmount(float dimAmount) {
mDimAmount = dimAmount;
updateBackgroundDimming();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java
new file mode 100644
index 0000000..1c010b6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayoutTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.stack;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.annotation.UiThread;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.NotificationHeaderView;
+import android.view.View;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.NotificationTestHelper;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.StatusBar;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NotificationStackScrollLayoutTest extends SysuiTestCase {
+
+ private NotificationStackScrollLayout mStackScroller;
+ private StatusBar mBar;
+
+ @Before
+ @UiThreadTest
+ public void setUp() throws Exception {
+ mStackScroller = new NotificationStackScrollLayout(getContext());
+ mBar = mock(StatusBar.class);
+ mStackScroller.setStatusBar(mBar);
+ mStackScroller.setScrimController(mock(ScrimController.class));
+ }
+
+ @Test
+ public void testNotDimmedOnKeyguard() {
+ when(mBar.getBarState()).thenReturn(StatusBarState.SHADE);
+ mStackScroller.setDimmed(true /* dimmed */, false /* animate */);
+ mStackScroller.setDimmed(true /* dimmed */, true /* animate */);
+ Assert.assertFalse(mStackScroller.isDimmed());
+ }
+
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5c5a6b7..9727092 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8063,7 +8063,7 @@
stack.setPictureInPictureActions(actions);
MetricsLogger.action(mContext, MetricsEvent.ACTION_PICTURE_IN_PICTURE_ENTERED,
- r.supportsPictureInPictureWhilePausing);
+ r.supportsEnterPipOnTaskSwitch);
logPictureInPictureArgs(params);
};
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 874bd1e..871ccb9 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -298,9 +298,8 @@
boolean frozenBeforeDestroy;// has been frozen but not yet destroyed.
boolean immersive; // immersive mode (don't interrupt if possible)
boolean forceNewConfig; // force re-create with new config next time
- boolean supportsPictureInPictureWhilePausing; // This flag is set by the system to indicate
- // that the activity can enter picture in picture while pausing (ie. only when another
- // task is brought to front or started)
+ boolean supportsEnterPipOnTaskSwitch; // This flag is set by the system to indicate that the
+ // activity can enter picture in picture while pausing (only when switching to another task)
PictureInPictureParams pictureInPictureArgs = new PictureInPictureParams.Builder().build();
// The PiP params used when deferring the entering of picture-in-picture.
int launchCount; // count of launches since last state
@@ -545,8 +544,8 @@
+ " mLastReportedPictureInPictureMode=" + mLastReportedPictureInPictureMode);
if (info.supportsPictureInPicture()) {
pw.println(prefix + "supportsPictureInPicture=" + info.supportsPictureInPicture());
- pw.println(prefix + "supportsPictureInPictureWhilePausing: "
- + supportsPictureInPictureWhilePausing);
+ pw.println(prefix + "supportsEnterPipOnTaskSwitch: "
+ + supportsEnterPipOnTaskSwitch);
}
if (info.maxAspectRatio != 0) {
pw.println(prefix + "maxAspectRatio=" + info.maxAspectRatio);
@@ -1220,19 +1219,19 @@
// When visible, allow entering PiP if the app is not locked. If it is over the
// keyguard, then we will prompt to unlock in the caller before entering PiP.
return !isCurrentAppLocked &&
- (supportsPictureInPictureWhilePausing || !beforeStopping);
+ (supportsEnterPipOnTaskSwitch || !beforeStopping);
case PAUSING:
case PAUSED:
// When pausing, then only allow enter PiP as in the resume state, and in addition,
// require that there is not an existing PiP activity and that the current system
// state supports entering PiP
return isNotLockedOrOnKeyguard && !hasPinnedStack
- && supportsPictureInPictureWhilePausing;
+ && supportsEnterPipOnTaskSwitch;
case STOPPING:
// When stopping in a valid state, then only allow enter PiP as in the pause state.
// Otherwise, fall through to throw an exception if the caller is trying to enter
// PiP in an invalid stopping state.
- if (supportsPictureInPictureWhilePausing) {
+ if (supportsEnterPipOnTaskSwitch) {
return isNotLockedOrOnKeyguard && !hasPinnedStack;
}
default:
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index eb3177a..adf5a50 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -2095,7 +2095,7 @@
// Reset the flag indicating that an app can enter picture-in-picture once the
// activity is hidden
- r.supportsPictureInPictureWhilePausing = false;
+ r.supportsEnterPipOnTaskSwitch = false;
break;
case INITIALIZING:
@@ -2917,11 +2917,9 @@
// supporting picture-in-picture while pausing only if the starting activity
// would not be considered an overlay on top of the current activity
// (eg. not fullscreen, or the assistant)
- if (focusedTopActivity != null
- && focusedTopActivity.getStackId() != PINNED_STACK_ID
- && r.getStackId() != ASSISTANT_STACK_ID
- && r.fullscreen) {
- focusedTopActivity.supportsPictureInPictureWhilePausing = true;
+ if (canEnterPipOnTaskSwitch(focusedTopActivity,
+ null /* toFrontTask */, r, options)) {
+ focusedTopActivity.supportsEnterPipOnTaskSwitch = true;
}
transit = TRANSIT_TASK_OPEN;
}
@@ -2976,6 +2974,37 @@
}
}
+ /**
+ * @return Whether the switch to another task can trigger the currently running activity to
+ * enter PiP while it is pausing (if supported). Only one of {@param toFrontTask} or
+ * {@param toFrontActivity} should be set.
+ */
+ private boolean canEnterPipOnTaskSwitch(ActivityRecord pipCandidate,
+ TaskRecord toFrontTask, ActivityRecord toFrontActivity, ActivityOptions opts) {
+ if (opts != null && opts.disallowEnterPictureInPictureWhileLaunching()) {
+ // Ensure the caller has requested not to trigger auto-enter PiP
+ return false;
+ }
+ if (pipCandidate == null || pipCandidate.getStackId() == PINNED_STACK_ID) {
+ // Ensure that we do not trigger entering PiP an activity on the pinned stack
+ return false;
+ }
+ final int targetStackId = toFrontTask != null ? toFrontTask.getStackId()
+ : toFrontActivity.getStackId();
+ if (targetStackId == ASSISTANT_STACK_ID) {
+ // Ensure the task/activity being brought forward is not the assistant
+ return false;
+ }
+ final boolean isFullscreen = toFrontTask != null
+ ? toFrontTask.containsOnlyFullscreenActivities()
+ : toFrontActivity.fullscreen;
+ if (!isFullscreen) {
+ // Ensure the task/activity being brought forward is fullscreen
+ return false;
+ }
+ return true;
+ }
+
private boolean isTaskSwitch(ActivityRecord r,
ActivityRecord topFocusedActivity) {
return topFocusedActivity != null && r.getTask() != topFocusedActivity.getTask();
@@ -4551,9 +4580,9 @@
// If a new task is moved to the front, then mark the existing top activity as supporting
// picture-in-picture while paused only if the task would not be considered an oerlay on top
// of the current activity (eg. not fullscreen, or the assistant)
- if (topActivity != null && topActivity.getStackId() != PINNED_STACK_ID
- && tr.getStackId() != ASSISTANT_STACK_ID && tr.containsOnlyFullscreenActivities()) {
- topActivity.supportsPictureInPictureWhilePausing = true;
+ if (canEnterPipOnTaskSwitch(topActivity, tr, null /* toFrontActivity */,
+ options)) {
+ topActivity.supportsEnterPipOnTaskSwitch = true;
}
mStackSupervisor.resumeFocusedStackTopActivityLocked();
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 68a4ad9..4eedb25 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static android.Manifest.permission.ACTIVITY_EMBEDDING;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.Manifest.permission.START_ANY_ACTIVITY;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
@@ -35,6 +36,7 @@
import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
@@ -57,7 +59,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STATES;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONTAINERS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_FOCUS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_IDLE;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
@@ -135,7 +136,6 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
@@ -1651,7 +1651,7 @@
if (options.getLaunchTaskId() != INVALID_STACK_ID) {
final int startInTaskPerm = mService.checkPermission(START_TASKS_FROM_RECENTS,
callingPid, callingUid);
- if (startInTaskPerm != PERMISSION_GRANTED) {
+ if (startInTaskPerm == PERMISSION_DENIED) {
final String msg = "Permission Denial: starting " + intent.toString()
+ " from " + callerApp + " (pid=" + callingPid
+ ", uid=" + callingUid + ") with launchTaskId="
@@ -1704,9 +1704,9 @@
return true;
}
- if (activityDisplay.mDisplay.getType() == TYPE_VIRTUAL
- && activityDisplay.mDisplay.getOwnerUid() != SYSTEM_UID
- && activityDisplay.mDisplay.getOwnerUid() != aInfo.applicationInfo.uid) {
+ final int displayOwnerUid = activityDisplay.mDisplay.getOwnerUid();
+ if (activityDisplay.mDisplay.getType() == TYPE_VIRTUAL && displayOwnerUid != SYSTEM_UID
+ && displayOwnerUid != aInfo.applicationInfo.uid) {
// Limit launching on virtual displays, because their contents can be read from Surface
// by apps that created them.
if ((aInfo.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) == 0) {
@@ -1714,6 +1714,13 @@
+ " disallow launch on virtual display for not-embedded activity.");
return false;
}
+ // Check if the caller is allowed to embed activities from other apps.
+ if (mService.checkPermission(ACTIVITY_EMBEDDING, callingPid, callingUid)
+ == PERMISSION_DENIED) {
+ if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
+ + " disallow activity embedding without permission.");
+ return false;
+ }
}
if (!activityDisplay.isPrivate()) {
@@ -1724,7 +1731,7 @@
}
// Check if the caller is the owner of the display.
- if (activityDisplay.mDisplay.getOwnerUid() == callingUid) {
+ if (displayOwnerUid == callingUid) {
if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
+ " allow launch for owner of the display");
return true;
@@ -1769,7 +1776,7 @@
String callingPackage, int callingPid, int callingUid, boolean ignoreTargetSecurity) {
if (!ignoreTargetSecurity && mService.checkComponentPermission(activityInfo.permission,
callingPid, callingUid, activityInfo.applicationInfo.uid, activityInfo.exported)
- == PackageManager.PERMISSION_DENIED) {
+ == PERMISSION_DENIED) {
return ACTIVITY_RESTRICTION_PERMISSION;
}
@@ -1816,8 +1823,7 @@
return ACTIVITY_RESTRICTION_NONE;
}
- if (mService.checkPermission(permission, callingPid, callingUid) ==
- PackageManager.PERMISSION_DENIED) {
+ if (mService.checkPermission(permission, callingPid, callingUid) == PERMISSION_DENIED) {
return ACTIVITY_RESTRICTION_PERMISSION;
}
@@ -2984,7 +2990,7 @@
// while pausing because that changes the focused stack and may prevent the new
// starting activity from resuming.
if (moveHomeStackToFront && task.getTaskToReturnTo() == HOME_ACTIVITY_TYPE
- && (r.state == RESUMED || !r.supportsPictureInPictureWhilePausing)) {
+ && (r.state == RESUMED || !r.supportsEnterPipOnTaskSwitch)) {
// Move the home stack forward if the task we just moved to the pinned stack
// was launched from home so home should be visible behind it.
moveHomeStackToFront(reason);
@@ -3013,7 +3019,7 @@
// Reset the state that indicates it can enter PiP while pausing after we've moved it
// to the pinned stack
- r.supportsPictureInPictureWhilePausing = false;
+ r.supportsEnterPipOnTaskSwitch = false;
} finally {
mWindowManager.continueSurfaceLayout();
}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 51ed6b2..2f3b559 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -99,6 +99,7 @@
import com.android.server.content.SyncStorageEngine.AuthorityInfo;
import com.android.server.content.SyncStorageEngine.EndPoint;
import com.android.server.content.SyncStorageEngine.OnSyncRequestListener;
+import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -403,12 +404,21 @@
return (networkInfo != null) && networkInfo.isConnected();
}
+ private String getJobStats() {
+ JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
+ return "JobStats: "
+ + ((js == null) ? "(JobSchedulerInternal==null)"
+ : js.getPersistStats().toString());
+ }
+
private BroadcastReceiver mShutdownIntentReceiver =
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.w(TAG, "Writing sync state before shutdown...");
getSyncStorageEngine().writeAllState();
+
+ mLogger.log(getJobStats());
mLogger.log("Shutting down.");
}
};
@@ -504,13 +514,11 @@
}
}
}
- final int totalJobs = (mJobSchedulerInternal == null)
- ? -1 : mJobSchedulerInternal.countJobs();
final String summary = "Loaded persisted syncs: "
+ numPersistedPeriodicSyncs + " periodic syncs, "
+ numPersistedOneshotSyncs + " oneshot syncs, "
+ (pendingJobs.size()) + " total system server jobs, "
- + totalJobs + " total jobs.";
+ + getJobStats();
Slog.i(TAG, summary);
mLogger.log(summary);
@@ -720,7 +728,7 @@
// the account (they run before) which is the genie is out of the bottle.
whiteListExistingSyncAdaptersIfNeeded();
- mLogger.log("Sync manager initialized.");
+ mLogger.log("Sync manager initialized: " + Build.FINGERPRINT);
}
public void onStartUser(int userHandle) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 8269042..d0a1d9e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -31,6 +31,8 @@
import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Point;
import android.hardware.SensorManager;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.DisplayManagerInternal;
@@ -211,6 +213,12 @@
// The virtual display adapter, or null if not registered.
private VirtualDisplayAdapter mVirtualDisplayAdapter;
+ // The stable device screen height and width. These are not tied to a specific display, even
+ // the default display, because they need to be stable over the course of the device's entire
+ // life, even if the default display changes (e.g. a new monitor is plugged into a PC-like
+ // device).
+ private Point mStableDisplaySize = new Point();
+
// Viewports of the default display and the display that should receive touch
// input from an external source. Used by the input system.
private final DisplayViewport mDefaultViewport = new DisplayViewport();
@@ -284,7 +292,10 @@
// adapter is up so that we have it's configuration. We could load it lazily, but since
// we're going to have to read it in eventually we may as well do it here rather than after
// we've waited for the display to register itself with us.
- mPersistentDataStore.loadIfNeeded();
+ synchronized(mSyncRoot) {
+ mPersistentDataStore.loadIfNeeded();
+ loadStableDisplayValuesLocked();
+ }
mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS);
publishBinderService(Context.DISPLAY_SERVICE, new BinderService(),
@@ -346,6 +357,34 @@
return mHandler;
}
+ private void loadStableDisplayValuesLocked() {
+ final Point size = mPersistentDataStore.getStableDisplaySize();
+ if (size.x > 0 && size.y > 0) {
+ // Just set these values directly so we don't write the display persistent data again
+ // unnecessarily
+ mStableDisplaySize.set(size.x, size.y);
+ } else {
+ final Resources res = mContext.getResources();
+ final int width = res.getInteger(
+ com.android.internal.R.integer.config_stableDeviceDisplayWidth);
+ final int height = res.getInteger(
+ com.android.internal.R.integer.config_stableDeviceDisplayHeight);
+ if (width > 0 && height > 0) {
+ setStableDisplaySizeLocked(width, height);
+ }
+ }
+ }
+
+ private Point getStableDisplaySizeInternal() {
+ Point r = new Point();
+ synchronized (mSyncRoot) {
+ if (mStableDisplaySize.x > 0 && mStableDisplaySize.y > 0) {
+ r.set(mStableDisplaySize.x, mStableDisplaySize.y);
+ }
+ }
+ return r;
+ }
+
private void registerDisplayTransactionListenerInternal(
DisplayTransactionListener listener) {
// List is self-synchronized copy-on-write.
@@ -770,18 +809,6 @@
if (work != null) {
work.run();
}
- if (display != null && display.getPrimaryDisplayDeviceLocked() == device) {
- int colorMode = mPersistentDataStore.getColorMode(device);
- if (colorMode == Display.COLOR_MODE_INVALID) {
- if ((device.getDisplayDeviceInfoLocked().flags
- & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0) {
- colorMode = mDefaultDisplayDefaultColorMode;
- } else {
- colorMode = Display.COLOR_MODE_DEFAULT;
- }
- }
- display.setRequestedColorModeLocked(colorMode);
- }
scheduleTraversalLocked(false);
}
@@ -886,6 +913,11 @@
return null;
}
+ configureColorModeLocked(display, device);
+ if (isDefault) {
+ recordStableDisplayStatsIfNeededLocked(display);
+ }
+
mLogicalDisplays.put(displayId, display);
// Wake up waitForDefaultDisplay.
@@ -907,6 +939,40 @@
return displayId;
}
+ private void configureColorModeLocked(LogicalDisplay display, DisplayDevice device) {
+ if (display.getPrimaryDisplayDeviceLocked() == device) {
+ int colorMode = mPersistentDataStore.getColorMode(device);
+ if (colorMode == Display.COLOR_MODE_INVALID) {
+ if ((device.getDisplayDeviceInfoLocked().flags
+ & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0) {
+ colorMode = mDefaultDisplayDefaultColorMode;
+ } else {
+ colorMode = Display.COLOR_MODE_DEFAULT;
+ }
+ }
+ display.setRequestedColorModeLocked(colorMode);
+ }
+ }
+
+ // If we've never recorded stable device stats for this device before and they aren't
+ // explicitly configured, go ahead and record the stable device stats now based on the status
+ // of the default display at first boot.
+ private void recordStableDisplayStatsIfNeededLocked(LogicalDisplay d) {
+ if (mStableDisplaySize.x <= 0 && mStableDisplaySize.y <= 0) {
+ DisplayInfo info = d.getDisplayInfoLocked();
+ setStableDisplaySizeLocked(info.getNaturalWidth(), info.getNaturalHeight());
+ }
+ }
+
+ private void setStableDisplaySizeLocked(int width, int height) {
+ mStableDisplaySize = new Point(width, height);
+ try {
+ mPersistentDataStore.setStableDisplaySize(mStableDisplaySize);
+ } finally {
+ mPersistentDataStore.saveIfNeeded();
+ }
+ }
+
// Updates all existing logical displays given the current set of display devices.
// Removes invalid logical displays.
// Sends notifications if needed.
@@ -1166,6 +1232,8 @@
pw.println(" mDefaultDisplayDefaultColorMode=" + mDefaultDisplayDefaultColorMode);
pw.println(" mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
pw.println(" mWifiDisplayScanRequestCount=" + mWifiDisplayScanRequestCount);
+ pw.println(" mStableDisplaySize=" + mStableDisplaySize);
+
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
ipw.increaseIndent();
@@ -1378,6 +1446,19 @@
}
}
+ /**
+ * Returns the stable device display size, in pixels.
+ */
+ @Override // Binder call
+ public Point getStableDisplaySize() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return getStableDisplaySizeInternal();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
@Override // Binder call
public void registerCallback(IDisplayManagerCallback callback) {
if (callback == null) {
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index 47701b9..34c8e22 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -23,6 +23,7 @@
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import android.graphics.Point;
import android.hardware.display.WifiDisplay;
import android.util.AtomicFile;
import android.util.Slog;
@@ -60,6 +61,10 @@
* <color-mode>0</color-mode>
* </display>
* </display-states>
+ * <stable-device-values>
+ * <stable-display-height>1920<stable-display-height>
+ * <stable-display-width>1080<stable-display-width>
+ * </stable-device-values>
* </display-manager-state>
* </code>
*
@@ -75,6 +80,9 @@
private final HashMap<String, DisplayState> mDisplayStates =
new HashMap<String, DisplayState>();
+ // Display values which should be stable across the device's lifetime.
+ private final StableDeviceValues mStableDeviceValues = new StableDeviceValues();
+
// The atomic file used to safely read or write the file.
private final AtomicFile mAtomicFile;
@@ -162,6 +170,7 @@
}
public boolean forgetWifiDisplay(String deviceAddress) {
+ loadIfNeeded();
int index = findRememberedWifiDisplay(deviceAddress);
if (index >= 0) {
mRememberedWifiDisplays.remove(index);
@@ -204,6 +213,18 @@
return false;
}
+ public Point getStableDisplaySize() {
+ loadIfNeeded();
+ return mStableDeviceValues.getDisplaySize();
+ }
+
+ public void setStableDisplaySize(Point size) {
+ loadIfNeeded();
+ if (mStableDeviceValues.setDisplaySize(size)) {
+ setDirty();
+ }
+ }
+
private DisplayState getDisplayState(String uniqueId, boolean createIfAbsent) {
loadIfNeeded();
DisplayState state = mDisplayStates.get(uniqueId);
@@ -290,6 +311,9 @@
if (parser.getName().equals("display-states")) {
loadDisplaysFromXml(parser);
}
+ if (parser.getName().equals("stable-device-values")) {
+ mStableDeviceValues.loadFromXml(parser);
+ }
}
}
@@ -363,6 +387,9 @@
serializer.endTag(null, "display");
}
serializer.endTag(null, "display-states");
+ serializer.startTag(null, "stable-device-values");
+ mStableDeviceValues.saveToXml(serializer);
+ serializer.endTag(null, "stable-device-values");
serializer.endTag(null, "display-manager-state");
serializer.endDocument();
}
@@ -382,6 +409,8 @@
pw.println(" " + i++ + ": " + entry.getKey());
entry.getValue().dump(pw, " ");
}
+ pw.println(" StableDeviceValues:");
+ mStableDeviceValues.dump(pw, " ");
}
private static final class DisplayState {
@@ -417,8 +446,66 @@
serializer.endTag(null, "color-mode");
}
- private void dump(final PrintWriter pw, final String prefix) {
+ public void dump(final PrintWriter pw, final String prefix) {
pw.println(prefix + "ColorMode=" + mColorMode);
}
}
+
+ private static final class StableDeviceValues {
+ private int mWidth;
+ private int mHeight;
+
+ private Point getDisplaySize() {
+ return new Point(mWidth, mHeight);
+ }
+
+ public boolean setDisplaySize(Point r) {
+ if (mWidth != r.x || mHeight != r.y) {
+ mWidth = r.x;
+ mHeight = r.y;
+ return true;
+ }
+ return false;
+ }
+
+ public void loadFromXml(XmlPullParser parser) throws IOException, XmlPullParserException {
+ final int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ switch (parser.getName()) {
+ case "stable-display-width":
+ mWidth = loadIntValue(parser);
+ break;
+ case "stable-display-height":
+ mHeight = loadIntValue(parser);
+ break;
+ }
+ }
+ }
+
+ private static int loadIntValue(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ try {
+ String value = parser.nextText();
+ return Integer.parseInt(value);
+ } catch (NumberFormatException nfe) {
+ return 0;
+ }
+ }
+
+ public void saveToXml(XmlSerializer serializer) throws IOException {
+ if (mWidth > 0 && mHeight > 0) {
+ serializer.startTag(null, "stable-display-width");
+ serializer.text(Integer.toString(mWidth));
+ serializer.endTag(null, "stable-display-width");
+ serializer.startTag(null, "stable-display-height");
+ serializer.text(Integer.toString(mHeight));
+ serializer.endTag(null, "stable-display-height");
+ }
+ }
+
+ public void dump(final PrintWriter pw, final String prefix) {
+ pw.println(prefix + "StableDisplayWidth=" + mWidth);
+ pw.println(prefix + "StableDisplayHeight=" + mHeight);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/job/JobSchedulerInternal.java b/services/core/java/com/android/server/job/JobSchedulerInternal.java
index d52b3b7..095526d 100644
--- a/services/core/java/com/android/server/job/JobSchedulerInternal.java
+++ b/services/core/java/com/android/server/job/JobSchedulerInternal.java
@@ -38,8 +38,44 @@
void removeBackingUpUid(int uid);
void clearAllBackingUpUids();
+ JobStorePersistStats getPersistStats();
+
/**
- * @return the total number of jobs across all UIDs.
+ * Stats about the first load after boot and the most recent save.
+ * STOPSHIP Remove it and the relevant code once b/64536115 is fixed.
*/
- int countJobs();
+ public class JobStorePersistStats {
+ public int countAllJobsLoaded = -1;
+ public int countSystemServerJobsLoaded = -1;
+ public int countSystemSyncManagerJobsLoaded = -1;
+
+ public int countAllJobsSaved = -1;
+ public int countSystemServerJobsSaved = -1;
+ public int countSystemSyncManagerJobsSaved = -1;
+
+ public JobStorePersistStats() {
+ }
+
+ public JobStorePersistStats(JobStorePersistStats source) {
+ countAllJobsLoaded = source.countAllJobsLoaded;
+ countSystemServerJobsLoaded = source.countSystemServerJobsLoaded;
+ countSystemSyncManagerJobsLoaded = source.countSystemSyncManagerJobsLoaded;
+
+ countAllJobsSaved = source.countAllJobsSaved;
+ countSystemServerJobsSaved = source.countSystemServerJobsSaved;
+ countSystemSyncManagerJobsSaved = source.countSystemSyncManagerJobsSaved;
+ }
+
+ @Override
+ public String toString() {
+ return "FirstLoad: "
+ + countAllJobsLoaded + "/"
+ + countSystemServerJobsLoaded + "/"
+ + countSystemSyncManagerJobsLoaded
+ + " LastSave: "
+ + countAllJobsSaved + "/"
+ + countSystemServerJobsSaved + "/"
+ + countSystemSyncManagerJobsSaved;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 12087b0..3fc76c0 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -782,6 +782,11 @@
*
*/
public void cancelJobsForUid(int uid, String reason) {
+ if (uid == Process.SYSTEM_UID) {
+ // This really shouldn't happen.
+ Slog.wtfStack(TAG, "cancelJobsForUid() called for system uid");
+ return;
+ }
synchronized (mLock) {
final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
for (int i=0; i<jobsForUid.size(); i++) {
@@ -1837,9 +1842,9 @@
}
@Override
- public int countJobs() {
+ public JobStorePersistStats getPersistStats() {
synchronized (mLock) {
- return mJobs.size();
+ return new JobStorePersistStats(mJobs.getPersistStats());
}
}
}
@@ -2022,13 +2027,6 @@
@Override
public void cancelAll() throws RemoteException {
final int uid = Binder.getCallingUid();
- switch (uid) {
- case Process.SYSTEM_UID:
- // This really shouldn't happen.
- Slog.wtf(TAG, "JobScheduler.cancelAll() called for uid=" + uid);
- return;
- }
-
long ident = Binder.clearCallingIdentity();
try {
JobSchedulerService.this.cancelJobsForUid(uid, "cancelAll() called by app");
@@ -2470,6 +2468,9 @@
pw.print("mReportedActive="); pw.println(mReportedActive);
pw.print("mMaxActiveJobs="); pw.println(mMaxActiveJobs);
}
+ pw.println();
+ pw.print("PersistStats: ");
+ pw.println(mJobs.getPersistStats());
}
pw.println();
}
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 5317616..aa9f77c 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -24,6 +24,7 @@
import android.os.Environment;
import android.os.Handler;
import android.os.PersistableBundle;
+import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.format.DateUtils;
@@ -38,6 +39,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.server.IoThread;
+import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
import com.android.server.job.controllers.JobStatus;
import java.io.ByteArrayOutputStream;
@@ -89,6 +91,8 @@
private final Handler mIoHandler = IoThread.getHandler();
private static JobStore sSingleton;
+ private JobStorePersistStats mPersistInfo = new JobStorePersistStats();
+
/** Used by the {@link JobSchedulerService} to instantiate the JobStore. */
static JobStore initAndGet(JobSchedulerService jobManagerService) {
synchronized (sSingletonLock) {
@@ -199,6 +203,10 @@
return mJobSet.size();
}
+ public JobStorePersistStats getPersistStats() {
+ return mPersistInfo;
+ }
+
public int countJobsForUid(int uid) {
return mJobSet.countJobsForUid(uid);
}
@@ -336,6 +344,9 @@
}
private void writeJobsMapImpl(List<JobStatus> jobList) {
+ int numJobs = 0;
+ int numSystemJobs = 0;
+ int numSyncJobs = 0;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
XmlSerializer out = new FastXmlSerializer();
@@ -356,6 +367,14 @@
writeExecutionCriteriaToXml(out, jobStatus);
writeBundleToXml(jobStatus.getJob().getExtras(), out);
out.endTag(null, "job");
+
+ numJobs++;
+ if (jobStatus.getUid() == Process.SYSTEM_UID) {
+ numSystemJobs++;
+ if (isSyncJob(jobStatus)) {
+ numSyncJobs++;
+ }
+ }
}
out.endTag(null, "job-info");
out.endDocument();
@@ -373,6 +392,10 @@
if (DEBUG) {
Slog.d(TAG, "Error persisting bundle.", e);
}
+ } finally {
+ mPersistInfo.countAllJobsSaved = numJobs;
+ mPersistInfo.countSystemServerJobsSaved = numSystemJobs;
+ mPersistInfo.countSystemSyncManagerJobsSaved = numSyncJobs;
}
}
@@ -525,6 +548,11 @@
return Pair.create(earliest, latest);
}
+ private static boolean isSyncJob(JobStatus status) {
+ return com.android.server.content.SyncJobService.class.getName()
+ .equals(status.getServiceComponent().getClassName());
+ }
+
/**
* Runnable that reads list of persisted job from xml. This is run once at start up, so doesn't
* need to go through {@link JobStore#add(com.android.server.job.controllers.JobStatus)}.
@@ -545,6 +573,8 @@
@Override
public void run() {
int numJobs = 0;
+ int numSystemJobs = 0;
+ int numSyncJobs = 0;
try {
List<JobStatus> jobs;
FileInputStream fis = mJobsFile.openRead();
@@ -558,7 +588,14 @@
js.prepareLocked(am);
js.enqueueTime = now;
this.jobSet.add(js);
+
numJobs++;
+ if (js.getUid() == Process.SYSTEM_UID) {
+ numSystemJobs++;
+ if (isSyncJob(js)) {
+ numSyncJobs++;
+ }
+ }
}
}
}
@@ -569,6 +606,12 @@
}
} catch (XmlPullParserException | IOException e) {
Slog.wtf(TAG, "Error jobstore xml.", e);
+ } finally {
+ if (mPersistInfo.countAllJobsLoaded < 0) { // Only set them once.
+ mPersistInfo.countAllJobsLoaded = numJobs;
+ mPersistInfo.countSystemServerJobsLoaded = numSystemJobs;
+ mPersistInfo.countSystemSyncManagerJobsLoaded = numSyncJobs;
+ }
}
Slog.i(TAG, "Read " + numJobs + " jobs");
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index d39679d..4d2cf32 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -37,6 +37,7 @@
private static final String COMMAND_SP = "sp";
private static final String COMMAND_SET_DISABLED = "set-disabled";
private static final String COMMAND_VERIFY = "verify";
+ private static final String COMMAND_GET_DISABLED = "get-disabled";
private int mCurrentUserId;
private final LockPatternUtils mLockPatternUtils;
@@ -80,6 +81,9 @@
case COMMAND_VERIFY:
runVerify();
break;
+ case COMMAND_GET_DISABLED:
+ runGetDisabled();
+ break;
default:
getErrPrintWriter().println("Unknown command: " + cmd);
break;
@@ -156,6 +160,11 @@
getOutPrintWriter().println("Lock screen disabled set to " + disabled);
}
+ private void runGetDisabled() {
+ boolean isLockScreenDisabled = mLockPatternUtils.isLockScreenDisabled(mCurrentUserId);
+ getOutPrintWriter().println(isLockScreenDisabled);
+ }
+
private boolean checkCredential() throws RemoteException {
final boolean havePassword = mLockPatternUtils.isLockPasswordEnabled(mCurrentUserId);
final boolean havePattern = mLockPatternUtils.isLockPatternEnabled(mCurrentUserId);
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index d5d0250..34f1bfa 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -455,6 +455,11 @@
}
}
+ public void onUserRemoved(int user) {
+ mApproved.remove(user);
+ rebindServices(true);
+ }
+
public void onUserSwitched(int user) {
if (DEBUG) Slog.d(TAG, "onUserSwitched u=" + user);
if (Arrays.equals(mLastSeenProfileIds, mUserProfiles.getCurrentProfileIds())) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f60d923..3be998d 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -985,12 +985,17 @@
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
if (userId != USER_NULL) {
mUserProfiles.updateCache(context);
- readDefaultApprovedServices(userId);
+ if (!mUserProfiles.isManagedProfile(userId)) {
+ readDefaultApprovedServices(userId);
+ }
}
} else if (action.equals(Intent.ACTION_USER_REMOVED)) {
final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
mZenModeHelper.onUserRemoved(user);
mRankingHelper.onUserRemoved(user);
+ mListeners.onUserRemoved(user);
+ mConditionProviders.onUserRemoved(user);
+ mAssistants.onUserRemoved(user);
savePolicyFile();
} else if (action.equals(Intent.ACTION_USER_UNLOCKED)) {
final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 23d36988..9d0b44d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -21822,32 +21822,36 @@
}
}
- if (callingUid == Process.SHELL_UID
- && (pkgSetting.pkgFlags & ApplicationInfo.FLAG_TEST_ONLY) == 0) {
- // Shell can only change whole packages between ENABLED and DISABLED_USER states
- // unless it is a test package.
- int oldState = pkgSetting.getEnabled(userId);
- if (className == null
- &&
- (oldState == COMPONENT_ENABLED_STATE_DISABLED_USER
- || oldState == COMPONENT_ENABLED_STATE_DEFAULT
- || oldState == COMPONENT_ENABLED_STATE_ENABLED)
- &&
- (newState == COMPONENT_ENABLED_STATE_DISABLED_USER
- || newState == COMPONENT_ENABLED_STATE_DEFAULT
- || newState == COMPONENT_ENABLED_STATE_ENABLED)) {
- // ok
- } else {
- throw new SecurityException(
- "Shell cannot change component state for " + packageName + "/"
- + className + " to " + newState);
+ synchronized (mPackages) {
+ if (callingUid == Process.SHELL_UID
+ && (pkgSetting.pkgFlags & ApplicationInfo.FLAG_TEST_ONLY) == 0) {
+ // Shell can only change whole packages between ENABLED and DISABLED_USER states
+ // unless it is a test package.
+ int oldState = pkgSetting.getEnabled(userId);
+ if (className == null
+ &&
+ (oldState == COMPONENT_ENABLED_STATE_DISABLED_USER
+ || oldState == COMPONENT_ENABLED_STATE_DEFAULT
+ || oldState == COMPONENT_ENABLED_STATE_ENABLED)
+ &&
+ (newState == COMPONENT_ENABLED_STATE_DISABLED_USER
+ || newState == COMPONENT_ENABLED_STATE_DEFAULT
+ || newState == COMPONENT_ENABLED_STATE_ENABLED)) {
+ // ok
+ } else {
+ throw new SecurityException(
+ "Shell cannot change component state for " + packageName + "/"
+ + className + " to " + newState);
+ }
}
}
if (className == null) {
// We're dealing with an application/package level state change
- if (pkgSetting.getEnabled(userId) == newState) {
- // Nothing to do
- return;
+ synchronized (mPackages) {
+ if (pkgSetting.getEnabled(userId) == newState) {
+ // Nothing to do
+ return;
+ }
}
// If we're enabling a system stub, there's a little more work to do.
// Prior to enabling the package, we need to decompress the APK(s) to the
@@ -21961,41 +21965,45 @@
// Don't care about who enables an app.
callingPackage = null;
}
- pkgSetting.setEnabled(newState, userId, callingPackage);
- } else {
- // We're dealing with a component level state change
- // First, verify that this is a valid class name.
- PackageParser.Package pkg = pkgSetting.pkg;
- if (pkg == null || !pkg.hasComponentClassName(className)) {
- if (pkg != null &&
- pkg.applicationInfo.targetSdkVersion >=
- Build.VERSION_CODES.JELLY_BEAN) {
- throw new IllegalArgumentException("Component class " + className
- + " does not exist in " + packageName);
- } else {
- Slog.w(TAG, "Failed setComponentEnabledSetting: component class "
- + className + " does not exist in " + packageName);
- }
+ synchronized (mPackages) {
+ pkgSetting.setEnabled(newState, userId, callingPackage);
}
- switch (newState) {
- case COMPONENT_ENABLED_STATE_ENABLED:
- if (!pkgSetting.enableComponentLPw(className, userId)) {
- return;
+ } else {
+ synchronized (mPackages) {
+ // We're dealing with a component level state change
+ // First, verify that this is a valid class name.
+ PackageParser.Package pkg = pkgSetting.pkg;
+ if (pkg == null || !pkg.hasComponentClassName(className)) {
+ if (pkg != null &&
+ pkg.applicationInfo.targetSdkVersion >=
+ Build.VERSION_CODES.JELLY_BEAN) {
+ throw new IllegalArgumentException("Component class " + className
+ + " does not exist in " + packageName);
+ } else {
+ Slog.w(TAG, "Failed setComponentEnabledSetting: component class "
+ + className + " does not exist in " + packageName);
+ }
}
- break;
- case COMPONENT_ENABLED_STATE_DISABLED:
- if (!pkgSetting.disableComponentLPw(className, userId)) {
- return;
+ switch (newState) {
+ case COMPONENT_ENABLED_STATE_ENABLED:
+ if (!pkgSetting.enableComponentLPw(className, userId)) {
+ return;
+ }
+ break;
+ case COMPONENT_ENABLED_STATE_DISABLED:
+ if (!pkgSetting.disableComponentLPw(className, userId)) {
+ return;
+ }
+ break;
+ case COMPONENT_ENABLED_STATE_DEFAULT:
+ if (!pkgSetting.restoreComponentLPw(className, userId)) {
+ return;
+ }
+ break;
+ default:
+ Slog.e(TAG, "Invalid new component state: " + newState);
+ return;
}
- break;
- case COMPONENT_ENABLED_STATE_DEFAULT:
- if (!pkgSetting.restoreComponentLPw(className, userId)) {
- return;
- }
- break;
- default:
- Slog.e(TAG, "Invalid new component state: " + newState);
- return;
}
}
synchronized (mPackages) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index f67ebe4..4c2e469 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5552,6 +5552,7 @@
WindowManager.LayoutParams statusBarAttrs = mStatusBar.getAttrs();
boolean statusBarExpanded = statusBarAttrs.height == MATCH_PARENT
&& statusBarAttrs.width == MATCH_PARENT;
+ boolean topAppHidesStatusBar = topAppHidesStatusBar();
if (mForceStatusBar || mForceStatusBarFromKeyguard || mForceStatusBarTransparent
|| statusBarExpanded) {
if (DEBUG_LAYOUT) Slog.v(TAG, "Showing status bar: forced");
@@ -5572,16 +5573,7 @@
}
}
} else if (mTopFullscreenOpaqueWindowState != null) {
- final int fl = PolicyControl.getWindowFlags(null, lp);
- if (localLOGV) {
- Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw()
- + " shown position: "
- + mTopFullscreenOpaqueWindowState.getShownPositionLw());
- Slog.d(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs()
- + " lp.flags=0x" + Integer.toHexString(fl));
- }
- topIsFullscreen = (fl & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0
- || (mLastSystemUiFlags & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
+ topIsFullscreen = topAppHidesStatusBar;
// The subtle difference between the window for mTopFullscreenOpaqueWindowState
// and mTopIsFullscreen is that mTopIsFullscreen is set only if the window
// has the FLAG_FULLSCREEN set. Not sure if there is another way that to be the
@@ -5604,8 +5596,10 @@
if (mStatusBarController.setBarShowingLw(true)) {
changes |= FINISH_LAYOUT_REDO_LAYOUT;
}
+ topAppHidesStatusBar = false;
}
}
+ mStatusBarController.setTopAppHidesStatusBar(topAppHidesStatusBar);
}
if (mTopIsFullscreen != topIsFullscreen) {
@@ -5646,6 +5640,27 @@
}
/**
+ * @return Whether the top app should hide the statusbar based on the top fullscreen opaque
+ * window.
+ */
+ private boolean topAppHidesStatusBar() {
+ if (mTopFullscreenOpaqueWindowState == null) {
+ return false;
+ }
+ final int fl = PolicyControl.getWindowFlags(null,
+ mTopFullscreenOpaqueWindowState.getAttrs());
+ if (localLOGV) {
+ Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw()
+ + " shown position: "
+ + mTopFullscreenOpaqueWindowState.getShownPositionLw());
+ Slog.d(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs()
+ + " lp.flags=0x" + Integer.toHexString(fl));
+ }
+ return (fl & LayoutParams.FLAG_FULLSCREEN) != 0
+ || (mLastSystemUiFlags & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
+ }
+
+ /**
* Updates the occluded state of the Keyguard.
*
* @return Whether the flags have changed and we have to redo the layout.
diff --git a/services/core/java/com/android/server/policy/StatusBarController.java b/services/core/java/com/android/server/policy/StatusBarController.java
index 7d67b60..ecc88b5 100644
--- a/services/core/java/com/android/server/policy/StatusBarController.java
+++ b/services/core/java/com/android/server/policy/StatusBarController.java
@@ -112,6 +112,14 @@
View.STATUS_BAR_TRANSPARENT);
}
+
+ public void setTopAppHidesStatusBar(boolean hidesStatusBar) {
+ StatusBarManagerInternal statusbar = getStatusBarInternal();
+ if (statusbar != null) {
+ statusbar.setTopAppHidesStatusBar(hidesStatusBar);
+ }
+ }
+
@Override
protected boolean skipAnimation() {
return mWin.getAttrs().height == MATCH_PARENT;
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 866fdad..0884678 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -80,6 +80,13 @@
void setGlobalActionsListener(GlobalActionsListener listener);
void showGlobalActions();
+ /**
+ * Set whether the top app currently hides the statusbar.
+ *
+ * @param hidesStatusBar whether it is being hidden
+ */
+ void setTopAppHidesStatusBar(boolean hidesStatusBar);
+
boolean showShutdownUi(boolean isReboot, String requestString);
public interface GlobalActionsListener {
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index d31c230..34ac645 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -332,6 +332,15 @@
}
@Override
+ public void setTopAppHidesStatusBar(boolean hidesStatusBar) {
+ if (mBar != null) {
+ try {
+ mBar.setTopAppHidesStatusBar(hidesStatusBar);
+ } catch (RemoteException ex) {}
+ }
+ }
+
+ @Override
public boolean showShutdownUi(boolean isReboot, String reason) {
if (!mContext.getResources().getBoolean(R.bool.config_showSysuiShutdown)) {
return false;
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index b6b964b..1f0b2f0 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -74,6 +74,7 @@
import java.io.PrintWriter;
import java.lang.StringBuilder;
import java.text.SimpleDateFormat;
+import java.util.Arrays;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
@@ -414,7 +415,8 @@
@Override
public void registerListener(IVrStateCallbacks cb) {
- enforceCallerPermission(Manifest.permission.ACCESS_VR_MANAGER);
+ enforceCallerPermissionAnyOf(Manifest.permission.ACCESS_VR_MANAGER,
+ Manifest.permission.ACCESS_VR_STATE);
if (cb == null) {
throw new IllegalArgumentException("Callback binder object is null.");
}
@@ -424,7 +426,8 @@
@Override
public void unregisterListener(IVrStateCallbacks cb) {
- enforceCallerPermission(Manifest.permission.ACCESS_VR_MANAGER);
+ enforceCallerPermissionAnyOf(Manifest.permission.ACCESS_VR_MANAGER,
+ Manifest.permission.ACCESS_VR_STATE);
if (cb == null) {
throw new IllegalArgumentException("Callback binder object is null.");
}
@@ -434,7 +437,8 @@
@Override
public void registerPersistentVrStateListener(IPersistentVrStateCallbacks cb) {
- enforceCallerPermission(Manifest.permission.ACCESS_VR_MANAGER);
+ enforceCallerPermissionAnyOf(Manifest.permission.ACCESS_VR_MANAGER,
+ Manifest.permission.ACCESS_VR_STATE);
if (cb == null) {
throw new IllegalArgumentException("Callback binder object is null.");
}
@@ -444,7 +448,8 @@
@Override
public void unregisterPersistentVrStateListener(IPersistentVrStateCallbacks cb) {
- enforceCallerPermission(Manifest.permission.ACCESS_VR_MANAGER);
+ enforceCallerPermissionAnyOf(Manifest.permission.ACCESS_VR_MANAGER,
+ Manifest.permission.ACCESS_VR_STATE);
if (cb == null) {
throw new IllegalArgumentException("Callback binder object is null.");
}
@@ -454,19 +459,28 @@
@Override
public boolean getVrModeState() {
+ enforceCallerPermissionAnyOf(Manifest.permission.ACCESS_VR_MANAGER,
+ Manifest.permission.ACCESS_VR_STATE);
return VrManagerService.this.getVrMode();
}
@Override
+ public boolean getPersistentVrModeEnabled() {
+ enforceCallerPermissionAnyOf(Manifest.permission.ACCESS_VR_MANAGER,
+ Manifest.permission.ACCESS_VR_STATE);
+ return VrManagerService.this.getPersistentVrMode();
+ }
+
+ @Override
public void setPersistentVrModeEnabled(boolean enabled) {
- enforceCallerPermission(Manifest.permission.RESTRICTED_VR_ACCESS);
+ enforceCallerPermissionAnyOf(Manifest.permission.RESTRICTED_VR_ACCESS);
VrManagerService.this.setPersistentVrModeEnabled(enabled);
}
@Override
public void setVr2dDisplayProperties(
Vr2dDisplayProperties vr2dDisplayProp) {
- enforceCallerPermission(Manifest.permission.RESTRICTED_VR_ACCESS);
+ enforceCallerPermissionAnyOf(Manifest.permission.RESTRICTED_VR_ACCESS);
VrManagerService.this.setVr2dDisplayProperties(vr2dDisplayProp);
}
@@ -530,11 +544,21 @@
};
- private void enforceCallerPermission(String permission) {
- if (mContext.checkCallingOrSelfPermission(permission)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException("Caller does not hold the permission " + permission);
+ /**
+ * Enforces that at lease one of the specified permissions is held by the caller.
+ * Throws SecurityException if none of the specified permissions are held.
+ *
+ * @param permissions One or more permissions to check against.
+ */
+ private void enforceCallerPermissionAnyOf(String... permissions) {
+ for (String permission : permissions) {
+ if (mContext.checkCallingOrSelfPermission(permission)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
}
+ throw new SecurityException("Caller does not hold at least one of the permissions: "
+ + Arrays.toString(permissions));
}
/**
@@ -1204,4 +1228,10 @@
return mVrModeEnabled;
}
}
+
+ private boolean getPersistentVrMode() {
+ synchronized (mLock) {
+ return mPersistentVrModeEnabled;
+ }
+ }
}
diff --git a/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
index 87312f8..f9cbd16 100644
--- a/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
+++ b/services/core/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
@@ -74,10 +74,9 @@
hidl_handle handleFromFileDescriptor(base::unique_fd fd) {
hidl_handle h;
- NATIVE_HANDLE_DECLARE_STORAGE(storage, 0, 0);
static constexpr int kNumFds = 1;
static constexpr int kNumInts = 0;
- native_handle_t *nh = native_handle_init(storage, kNumFds, kNumInts);
+ native_handle_t *nh = native_handle_create(kNumFds, kNumInts);
nh->data[0] = fd.release();
static constexpr bool kTakeOwnership = true;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3c1262f..221b9b4 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -828,9 +828,11 @@
// because it need to connect to SensorManager. This have to start
// after START_SENSOR_SERVICE is done.
SystemServerInitThreadPool.get().submit(() -> {
- traceBeginAndSlog(START_HIDL_SERVICES);
+ TimingsTraceLog traceLog = new TimingsTraceLog(
+ SYSTEM_SERVER_TIMING_ASYNC_TAG, Trace.TRACE_TAG_SYSTEM_SERVER);
+ traceLog.traceBegin(START_HIDL_SERVICES);
startHidlServices();
- traceEnd();
+ traceLog.traceEnd();
}, START_HIDL_SERVICES);
if (!disableVrManager) {
diff --git a/services/tests/notification/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/notification/src/com/android/server/notification/ManagedServicesTest.java
index 8242149..613f01c 100644
--- a/services/tests/notification/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/notification/src/com/android/server/notification/ManagedServicesTest.java
@@ -91,6 +91,7 @@
private ArrayMap<Integer, String> mExpectedSecondaryPackages;
private ArrayMap<Integer, String> mExpectedSecondaryComponentNames;
+ // type : user : list of approved
private ArrayMap<Integer, ArrayMap<Integer, String>> mExpectedPrimary = new ArrayMap<>();
private ArrayMap<Integer, ArrayMap<Integer, String>> mExpectedSecondary = new ArrayMap<>();
@@ -578,6 +579,32 @@
assertEquals(0, service.getAllowedComponents(10).size());
}
+ @Test
+ public void testOnUserRemoved() throws Exception {
+ for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+ mIpm, approvalLevel);
+ loadXml(service);
+
+ ArrayMap<Integer, String> verifyMap = mExpectedPrimary.get(service.mApprovalLevel);
+ String user0 = verifyMap.remove(0);
+ verifyMap = mExpectedSecondary.get(service.mApprovalLevel);
+ user0 = user0 + ":" + verifyMap.remove(0);
+
+ service.onUserRemoved(0);
+
+ for (String verifyValue : user0.split(":")) {
+ if (!TextUtils.isEmpty(verifyValue)) {
+ assertFalse("service type " + service.mApprovalLevel + ":" + verifyValue
+ + " is still allowed",
+ service.isPackageOrComponentAllowed(verifyValue, 0));
+ }
+ }
+
+ verifyExpectedApprovedEntries(service);
+ }
+ }
+
private void loadXml(ManagedServices service) throws Exception {
final StringBuffer xml = new StringBuffer();
xml.append("<" + service.getConfig().xmlTag + ">\n");
@@ -657,7 +684,8 @@
for (String packageOrComponent : verifyMap.get(userId).split(":")) {
if (!TextUtils.isEmpty(packageOrComponent)) {
if (service.mApprovalLevel == APPROVAL_BY_PACKAGE) {
- assertTrue(packageOrComponent, service.isComponentEnabledForPackage(packageOrComponent));
+ assertTrue(packageOrComponent,
+ service.isComponentEnabledForPackage(packageOrComponent));
for (int i = 1; i <= 3; i ++) {
ComponentName componentName = ComponentName.unflattenFromString(
packageOrComponent +"/C" + i);
diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h
index 0dbf4ee..bf56ec0 100644
--- a/tools/aapt/SdkConstants.h
+++ b/tools/aapt/SdkConstants.h
@@ -42,7 +42,7 @@
SDK_NOUGAT = 24,
SDK_NOUGAT_MR1 = 25,
SDK_O = 26,
- SDK_O_MR1 = 10000, // STOPSHIP Replace with the real version.
+ SDK_O_MR1 = 27,
};
#endif // H_AAPT_SDK_CONSTANTS
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 864e57d..5c32ed4 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -52,7 +52,7 @@
SDK_NOUGAT = 24,
SDK_NOUGAT_MR1 = 25,
SDK_O = 26,
- SDK_O_MR1 = 10000, // STOPSHIP Replace with the real version.
+ SDK_O_MR1 = 27,
};
ApiVersion FindAttributeSdkLevel(const ResourceId& id);
diff --git a/wifi/java/android/net/wifi/WifiNetworkScoreCache.java b/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
index f61dfdc..b22ae070 100755
--- a/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
+++ b/wifi/java/android/net/wifi/WifiNetworkScoreCache.java
@@ -26,15 +26,14 @@
import android.os.Handler;
import android.os.Process;
import android.util.Log;
+import android.util.LruCache;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
/**
* {@link INetworkScoreCache} implementation for Wifi Networks.
@@ -50,18 +49,21 @@
// scorer to provide an RSSI threshold below which a network should not be used.
public static final int INVALID_NETWORK_SCORE = Byte.MIN_VALUE;
+ /** Default number entries to be stored in the {@link LruCache}. */
+ private static final int DEFAULT_MAX_CACHE_SIZE = 100;
+
// See {@link #CacheListener}.
@Nullable
- @GuardedBy("mCacheLock")
+ @GuardedBy("mLock")
private CacheListener mListener;
private final Context mContext;
- private final Object mCacheLock = new Object();
+ private final Object mLock = new Object();
// The key is of the form "<ssid>"<bssid>
// TODO: What about SSIDs that can't be encoded as UTF-8?
- private final Map<String, ScoredNetwork> mNetworkCache;
-
+ @GuardedBy("mLock")
+ private final LruCache<String, ScoredNetwork> mCache;
public WifiNetworkScoreCache(Context context) {
this(context, null /* listener */);
@@ -74,9 +76,14 @@
* @param listener CacheListener for cache updates
*/
public WifiNetworkScoreCache(Context context, @Nullable CacheListener listener) {
+ this(context, listener, DEFAULT_MAX_CACHE_SIZE);
+ }
+
+ public WifiNetworkScoreCache(
+ Context context, @Nullable CacheListener listener, int maxCacheSize) {
mContext = context.getApplicationContext();
mListener = listener;
- mNetworkCache = new HashMap<>();
+ mCache = new LruCache<>(maxCacheSize);
}
@Override public final void updateScores(List<ScoredNetwork> networks) {
@@ -89,7 +96,7 @@
boolean changed = false;
- synchronized(mNetworkCache) {
+ synchronized(mLock) {
for (ScoredNetwork network : networks) {
String networkKey = buildNetworkKey(network);
if (networkKey == null) {
@@ -98,12 +105,10 @@
}
continue;
}
- mNetworkCache.put(networkKey, network);
+ mCache.put(networkKey, network);
changed = true;
}
- }
- synchronized (mCacheLock) {
if (mListener != null && changed) {
mListener.post(networks);
}
@@ -111,8 +116,8 @@
}
@Override public final void clearScores() {
- synchronized (mNetworkCache) {
- mNetworkCache.clear();
+ synchronized (mLock) {
+ mCache.evictAll();
}
}
@@ -138,7 +143,6 @@
}
public int getNetworkScore(ScanResult result) {
-
int score = INVALID_NETWORK_SCORE;
ScoredNetwork network = getScoredNetwork(result);
@@ -164,7 +168,6 @@
}
public int getNetworkScore(ScanResult result, boolean isActiveNetwork) {
-
int score = INVALID_NETWORK_SCORE;
ScoredNetwork network = getScoredNetwork(result);
@@ -185,8 +188,8 @@
String key = buildNetworkKey(result);
if (key == null) return null;
- synchronized(mNetworkCache) {
- ScoredNetwork network = mNetworkCache.get(key);
+ synchronized(mLock) {
+ ScoredNetwork network = mCache.get(key);
return network;
}
}
@@ -201,8 +204,8 @@
}
return null;
}
- synchronized (mNetworkCache) {
- return mNetworkCache.get(key);
+ synchronized (mLock) {
+ return mCache.get(key);
}
}
@@ -248,33 +251,35 @@
mContext.getPackageName(), Process.myUid());
writer.println(header);
writer.println(" All score curves:");
- for (ScoredNetwork score : mNetworkCache.values()) {
- writer.println(" " + score);
- }
- writer.println(" Current network scores:");
- WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
- for (ScanResult scanResult : wifiManager.getScanResults()) {
- writer.println(" " + buildNetworkKey(scanResult) + ": " + getNetworkScore(scanResult));
+ synchronized (mLock) {
+ for (ScoredNetwork score : mCache.snapshot().values()) {
+ writer.println(" " + score);
+ }
+ writer.println(" Network scores for latest ScanResults:");
+ WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ for (ScanResult scanResult : wifiManager.getScanResults()) {
+ writer.println(
+ " " + buildNetworkKey(scanResult) + ": " + getNetworkScore(scanResult));
+ }
}
}
/** Registers a CacheListener instance, replacing the previous listener if it existed. */
public void registerListener(CacheListener listener) {
- synchronized (mCacheLock) {
+ synchronized (mLock) {
mListener = listener;
}
}
/** Removes the registered CacheListener. */
public void unregisterListener() {
- synchronized (mCacheLock) {
+ synchronized (mLock) {
mListener = null;
}
}
/** Listener for updates to the cache inside WifiNetworkScoreCache. */
public abstract static class CacheListener {
-
private Handler mHandler;
/**
diff --git a/wifi/tests/Android.mk b/wifi/tests/Android.mk
index afab1a3..c98e40a 100644
--- a/wifi/tests/Android.mk
+++ b/wifi/tests/Android.mk
@@ -49,14 +49,15 @@
LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-test \
- core-test-rules \
- guava \
- mockito-target-minus-junit4 \
- frameworks-base-testutils \
+ android-support-test \
+ core-test-rules \
+ guava \
+ mockito-target-minus-junit4 \
+ frameworks-base-testutils \
+ truth-prebuilt \
LOCAL_JAVA_LIBRARIES := \
- android.test.runner \
+ android.test.runner \
LOCAL_PACKAGE_NAME := FrameworksWifiApiTests
LOCAL_COMPATIBILITY_SUITE := device-tests
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java
index 18f6bc8..997282b 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java
@@ -16,9 +16,9 @@
package android.net.wifi;
-import static org.junit.Assert.*;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -34,12 +34,9 @@
import com.google.common.collect.ImmutableList;
-import org.junit.Rule;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -54,7 +51,11 @@
public class WifiNetworkScoreCacheTest {
public static final String SSID = "ssid";
+ public static final String SSID2 = "ssid2";
+ public static final String SSID3 = "ssid3";
public static final String FORMATTED_SSID = "\"" + SSID + "\"";
+ public static final String FORMATTED_SSID2 = "\"" + SSID2 + "\"";
+ public static final String FORMATTED_SSID3 = "\"" + SSID3 + "\"";
public static final String BSSID = "AA:AA:AA:AA:AA:AA";
public static final WifiKey VALID_KEY = new WifiKey(FORMATTED_SSID, BSSID);
@@ -120,13 +121,13 @@
@Test
public void isScoredNetworkShouldReturnTrueAfterUpdateScoresIsCalled() {
- assertTrue(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT));
+ assertThat(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT)).isTrue();
}
@Test
public void isScoredNetworkShouldReturnFalseAfterClearScoresIsCalled() {
mScoreCache.clearScores();
- assertFalse(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT));
+ assertThat(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT)).isFalse();
}
@Test
@@ -137,19 +138,19 @@
mScoreCache.updateScores(ImmutableList.of(network2));
- assertTrue(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT));
- assertTrue(mScoreCache.isScoredNetwork(result2));
+ assertThat(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT)).isTrue();
+ assertThat(mScoreCache.isScoredNetwork(result2)).isTrue();
}
@Test
public void hasScoreCurveShouldReturnTrue() {
- assertTrue(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT));
+ assertThat(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT)).isTrue();
}
@Test
public void hasScoreCurveShouldReturnFalseWhenNoCachedNetwork() {
ScanResult unscored = buildScanResult("fake", BSSID);
- assertFalse(mScoreCache.hasScoreCurve(unscored));
+ assertThat(mScoreCache.hasScoreCurve(unscored)).isFalse();
}
@Test
@@ -157,7 +158,7 @@
ScoredNetwork noCurve = buildScoredNetwork(VALID_KEY, null /* rssiCurve */);
mScoreCache.updateScores(ImmutableList.of(noCurve));
- assertFalse(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT));
+ assertThat(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT)).isFalse();
}
@Test
@@ -169,12 +170,12 @@
when(mockRssiCurve.lookupScore(rssi)).thenReturn(score);
- assertEquals(score, mScoreCache.getNetworkScore(result));
+ assertThat(mScoreCache.getNetworkScore(result)).isEqualTo(score);
}
@Test
public void getMeteredHintShouldReturnFalse() {
- assertFalse(mScoreCache.getMeteredHint(VALID_SCAN_RESULT));
+ assertThat(mScoreCache.getMeteredHint(VALID_SCAN_RESULT)).isFalse();
}
@Test
@@ -184,7 +185,7 @@
new NetworkKey(VALID_KEY), mockRssiCurve, true /* metered Hint */);
mScoreCache.updateScores(ImmutableList.of(network));
- assertTrue(mScoreCache.getMeteredHint(VALID_SCAN_RESULT));
+ assertThat(mScoreCache.getMeteredHint(VALID_SCAN_RESULT)).isTrue();
}
@Test
@@ -197,7 +198,28 @@
} catch (InterruptedException e) {
fail("Interrupted Exception while waiting for listener to be invoked.");
}
- assertEquals("One network should be updated", 1, mUpdatedNetworksCaptor.size());
- assertEquals(mValidScoredNetwork, mUpdatedNetworksCaptor.get(0));
+ // One network should be updated.
+ assertThat(mUpdatedNetworksCaptor.size()).isEqualTo(1);
+ assertThat(mUpdatedNetworksCaptor.get(0)).isEqualTo(mValidScoredNetwork);
+ }
+
+ @Test
+ public void leastRecentlyUsedScore_shouldBeEvictedFromCache() {
+ mScoreCache = new WifiNetworkScoreCache(mockContext, mCacheListener, 2 /* maxCacheSize */);
+
+ ScoredNetwork network1 = mValidScoredNetwork;
+ ScoredNetwork network2 = buildScoredNetwork(
+ new WifiKey(FORMATTED_SSID2, BSSID), mockRssiCurve);
+ ScoredNetwork network3 = buildScoredNetwork(
+ new WifiKey(FORMATTED_SSID3, BSSID), mockRssiCurve);
+ mScoreCache.updateScores(ImmutableList.of(network1));
+ mScoreCache.updateScores(ImmutableList.of(network2));
+
+ // First score should be evicted because max cache size has been reached.
+ mScoreCache.updateScores(ImmutableList.of(network3));
+
+ assertThat(mScoreCache.hasScoreCurve(buildScanResult(SSID2, BSSID))).isTrue();
+ assertThat(mScoreCache.hasScoreCurve(buildScanResult(SSID3, BSSID))).isTrue();
+ assertThat(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT)).isFalse();
}
}