Merge "Switch how destroyHardwareResources works"
diff --git a/Android.mk b/Android.mk
index 43dbef6..e68b310 100644
--- a/Android.mk
+++ b/Android.mk
@@ -306,6 +306,7 @@
core/java/android/service/wallpaper/IWallpaperService.aidl \
core/java/android/service/chooser/IChooserTargetService.aidl \
core/java/android/service/chooser/IChooserTargetResult.aidl \
+ core/java/android/text/ITextClassificationService.aidl \
core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl\
core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl\
core/java/android/view/accessibility/IAccessibilityManager.aidl \
diff --git a/api/current.txt b/api/current.txt
index c7fcc26..805586e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5423,6 +5423,7 @@
method public boolean canShowBadge();
method public int describeContents();
method public void enableVibration(boolean);
+ method public android.media.AudioAttributes getAudioAttributes();
method public java.lang.String getGroup();
method public java.lang.String getId();
method public int getImportance();
@@ -5436,7 +5437,7 @@
method public void setLights(boolean);
method public void setLockscreenVisibility(int);
method public void setShowBadge(boolean);
- method public void setSound(android.net.Uri);
+ method public void setSound(android.net.Uri, android.media.AudioAttributes);
method public void setVibrationPattern(long[]);
method public boolean shouldShowLights();
method public boolean shouldVibrate();
diff --git a/api/system-current.txt b/api/system-current.txt
index 02432f0..64e9571 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5599,6 +5599,7 @@
method public boolean canShowBadge();
method public int describeContents();
method public void enableVibration(boolean);
+ method public android.media.AudioAttributes getAudioAttributes();
method public java.lang.String getGroup();
method public java.lang.String getId();
method public int getImportance();
@@ -5617,7 +5618,7 @@
method public void setLights(boolean);
method public void setLockscreenVisibility(int);
method public void setShowBadge(boolean);
- method public void setSound(android.net.Uri);
+ method public void setSound(android.net.Uri, android.media.AudioAttributes);
method public void setVibrationPattern(long[]);
method public boolean shouldShowLights();
method public boolean shouldVibrate();
@@ -5628,6 +5629,7 @@
field public static final java.lang.String DEFAULT_CHANNEL_ID = "miscellaneous";
field public static final int[] LOCKABLE_FIELDS;
field public static final int USER_LOCKED_ALLOWED = 64; // 0x40
+ field public static final int USER_LOCKED_AUDIO_ATTRIBUTES = 256; // 0x100
field public static final int USER_LOCKED_IMPORTANCE = 4; // 0x4
field public static final int USER_LOCKED_LIGHTS = 8; // 0x8
field public static final int USER_LOCKED_PRIORITY = 1; // 0x1
diff --git a/api/test-current.txt b/api/test-current.txt
index b599c1c2..a32300e 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5433,6 +5433,7 @@
method public boolean canShowBadge();
method public int describeContents();
method public void enableVibration(boolean);
+ method public android.media.AudioAttributes getAudioAttributes();
method public java.lang.String getGroup();
method public java.lang.String getId();
method public int getImportance();
@@ -5446,7 +5447,7 @@
method public void setLights(boolean);
method public void setLockscreenVisibility(int);
method public void setShowBadge(boolean);
- method public void setSound(android.net.Uri);
+ method public void setSound(android.net.Uri, android.media.AudioAttributes);
method public void setVibrationPattern(long[]);
method public boolean shouldShowLights();
method public boolean shouldVibrate();
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 26ec418..58ff496 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -22,6 +22,7 @@
import android.annotation.SystemApi;
import android.app.NotificationManager;
+import android.media.AudioAttributes;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -55,8 +56,9 @@
private static final String ATT_VIBRATION = "vibration";
private static final String ATT_VIBRATION_ENABLED = "vibration_enabled";
private static final String ATT_SOUND = "sound";
- //TODO: add audio attributes support
- private static final String ATT_AUDIO_ATTRIBUTES = "audio_attributes";
+ private static final String ATT_USAGE = "usage";
+ private static final String ATT_FLAGS = "flags";
+ private static final String ATT_CONTENT_TYPE = "content_type";
private static final String ATT_SHOW_BADGE = "show_badge";
private static final String ATT_USER_LOCKED = "locked";
private static final String ATT_GROUP = "group";
@@ -109,6 +111,12 @@
* @hide
*/
@SystemApi
+ public static final int USER_LOCKED_AUDIO_ATTRIBUTES = 0x00000100;
+
+ /**
+ * @hide
+ */
+ @SystemApi
public static final int[] LOCKABLE_FIELDS = new int[] {
USER_LOCKED_PRIORITY,
USER_LOCKED_VISIBILITY,
@@ -117,7 +125,8 @@
USER_LOCKED_VIBRATION,
USER_LOCKED_SOUND,
USER_LOCKED_ALLOWED,
- USER_LOCKED_SHOW_BADGE
+ USER_LOCKED_SHOW_BADGE,
+ USER_LOCKED_AUDIO_ATTRIBUTES
};
@@ -141,6 +150,7 @@
private boolean mShowBadge = DEFAULT_SHOW_BADGE;
private boolean mDeleted = DEFAULT_DELETED;
private String mGroup;
+ private AudioAttributes mAudioAttributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
/**
* Creates a notification channel.
@@ -183,6 +193,7 @@
} else {
mGroup = null;
}
+ mAudioAttributes = in.readInt() > 0 ? AudioAttributes.CREATOR.createFromParcel(in) : null;
}
@Override
@@ -215,6 +226,12 @@
} else {
dest.writeByte((byte) 0);
}
+ if (mAudioAttributes != null) {
+ dest.writeInt(1);
+ mAudioAttributes.writeToParcel(dest, 0);
+ } else {
+ dest.writeInt(0);
+ }
}
/**
@@ -275,6 +292,9 @@
*
* Group information is only used for presentation, not for behavior.
*
+ * Only modifiable before the channel is submitted to
+ * {@link NotificationManager#notify(String, int, Notification)}.
+ *
* @param groupId the id of a group created by
* {@link NotificationManager#createNotificationChannelGroup(NotificationChannelGroup)}.
*/
@@ -293,18 +313,23 @@
}
/**
- * Sets the sound that should be played for notifications posted to this channel if
- * the notifications don't supply a sound. Only modifiable before the channel is submitted
- * to the NotificationManager.
+ * Sets the sound that should be played for notifications posted to this channel and its
+ * audio attributes.
+ *
+ * Only modifiable before the channel is submitted to
+ * {@link NotificationManager#notify(String, int, Notification)}.
*/
- public void setSound(Uri sound) {
+ public void setSound(Uri sound, AudioAttributes audioAttributes) {
this.mSound = sound;
+ this.mAudioAttributes = audioAttributes;
}
/**
* Sets whether notifications posted to this channel should display notification lights,
- * on devices that support that feature. Only modifiable before the channel is submitted to
- * the NotificationManager.
+ * on devices that support that feature.
+ *
+ * Only modifiable before the channel is submitted to
+ * {@link NotificationManager#notify(String, int, Notification)}.
*/
public void setLights(boolean lights) {
this.mLights = lights;
@@ -312,16 +337,20 @@
/**
* Sets whether notification posted to this channel should vibrate. The vibration pattern can
- * be set with {@link #setVibrationPattern(long[])}. Only modifiable before the channel is
- * submitted to the NotificationManager.
+ * be set with {@link #setVibrationPattern(long[])}.
+ *
+ * Only modifiable before the channel is submitted to
+ * {@link NotificationManager#notify(String, int, Notification)}.
*/
public void enableVibration(boolean vibration) {
this.mVibrationEnabled = vibration;
}
/**
- * Sets whether notification posted to this channel should vibrate. Only modifiable before the
- * channel is submitted to the NotificationManager.
+ * Sets whether notification posted to this channel should vibrate.
+ *
+ * Only modifiable before the channel is submitted to
+ * {@link NotificationManager#notify(String, int, Notification)}.
*/
public void setVibrationPattern(long[] vibrationPattern) {
this.mVibration = vibrationPattern;
@@ -365,6 +394,13 @@
}
/**
+ * Returns the audio attributes for sound played by notifications posted to this channel.
+ */
+ public AudioAttributes getAudioAttributes() {
+ return mAudioAttributes;
+ }
+
+ /**
* Returns whether notifications posted to this channel trigger notification lights.
*/
public boolean shouldShowLights() {
@@ -438,7 +474,7 @@
setBypassDnd(Notification.PRIORITY_DEFAULT
!= safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT));
setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
- setSound(safeUri(parser, ATT_SOUND));
+ setSound(safeUri(parser, ATT_SOUND), safeAudioAttributes(parser));
setLights(safeBool(parser, ATT_LIGHTS, false));
enableVibration(safeBool(parser, ATT_VIBRATION_ENABLED, false));
setVibrationPattern(safeLongArray(parser, ATT_VIBRATION, null));
@@ -471,6 +507,12 @@
if (getSound() != null) {
out.attribute(null, ATT_SOUND, getSound().toString());
}
+ if (getAudioAttributes() != null) {
+ out.attribute(null, ATT_USAGE, Integer.toString(getAudioAttributes().getUsage()));
+ out.attribute(null, ATT_CONTENT_TYPE,
+ Integer.toString(getAudioAttributes().getContentType()));
+ out.attribute(null, ATT_FLAGS, Integer.toString(getAudioAttributes().getFlags()));
+ }
if (shouldShowLights()) {
out.attribute(null, ATT_LIGHTS, Boolean.toString(shouldShowLights()));
}
@@ -517,6 +559,12 @@
if (getSound() != null) {
record.put(ATT_SOUND, getSound().toString());
}
+ if (getAudioAttributes() != null) {
+ record.put(ATT_USAGE, Integer.toString(getAudioAttributes().getUsage()));
+ record.put(ATT_CONTENT_TYPE,
+ Integer.toString(getAudioAttributes().getContentType()));
+ record.put(ATT_FLAGS, Integer.toString(getAudioAttributes().getFlags()));
+ }
record.put(ATT_LIGHTS, Boolean.toString(shouldShowLights()));
record.put(ATT_VIBRATION_ENABLED, Boolean.toString(shouldVibrate()));
record.put(ATT_USER_LOCKED, Integer.toString(getUserLockedFields()));
@@ -527,6 +575,18 @@
return record;
}
+ private static AudioAttributes safeAudioAttributes(XmlPullParser parser) {
+ int usage = safeInt(parser, ATT_USAGE, AudioAttributes.USAGE_NOTIFICATION);
+ int contentType = safeInt(parser, ATT_CONTENT_TYPE,
+ AudioAttributes.CONTENT_TYPE_SONIFICATION);
+ int flags = safeInt(parser, ATT_FLAGS, 0);
+ return new AudioAttributes.Builder()
+ .setUsage(usage)
+ .setContentType(contentType)
+ .setFlags(flags)
+ .build();
+ }
+
private static Uri safeUri(XmlPullParser parser, String att) {
final String val = parser.getAttributeValue(null, att);
return val == null ? null : Uri.parse(val);
@@ -618,7 +678,11 @@
return false;
}
if (!Arrays.equals(mVibration, that.mVibration)) return false;
- return getGroup() != null ? getGroup().equals(that.getGroup()) : that.getGroup() == null;
+ if (getGroup() != null ? !getGroup().equals(that.getGroup()) : that.getGroup() != null) {
+ return false;
+ }
+ return getAudioAttributes() != null ? getAudioAttributes().equals(that.getAudioAttributes())
+ : that.getAudioAttributes() == null;
}
@@ -637,6 +701,7 @@
result = 31 * result + (mShowBadge ? 1 : 0);
result = 31 * result + (isDeleted() ? 1 : 0);
result = 31 * result + (getGroup() != null ? getGroup().hashCode() : 0);
+ result = 31 * result + (getAudioAttributes() != null ? getAudioAttributes().hashCode() : 0);
return result;
}
@@ -656,6 +721,7 @@
", mShowBadge=" + mShowBadge +
", mDeleted=" + mDeleted +
", mGroup='" + mGroup + '\'' +
+ ", mAudioAttributes=" + mAudioAttributes +
'}';
}
}
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 885b42f3..4534767 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1184,6 +1184,25 @@
}
/**
+ * Get the end time of the latest remote device discovery process.
+ * @return the latest time that the bluetooth adapter was/will be in discovery mode,
+ * in milliseconds since the epoch.
+ * This time can be in the future if {@link #startDiscovery()} has been called recently.
+ * @hide
+ */
+ public long getDiscoveryEndMillis() {
+ try {
+ mServiceLock.readLock().lock();
+ if (mService != null) return mService.getDiscoveryEndMillis();
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ } finally {
+ mServiceLock.readLock().unlock();
+ }
+ return -1;
+ }
+
+ /**
* Start the remote device discovery process.
* <p>The discovery process usually involves an inquiry scan of about 12
* seconds, followed by a page scan of each new device to retrieve its
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index 7c5458b..53fef2a 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -52,6 +52,7 @@
boolean startDiscovery();
boolean cancelDiscovery();
boolean isDiscovering();
+ long getDiscoveryEndMillis();
int getAdapterConnectionState();
int getProfileConnectionState(int profile);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 61531ae..4cf65ab 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -490,6 +490,7 @@
*/
boolean performDexOpt(String packageName, boolean checkProfiles,
int compileReason, boolean force);
+
/**
* Ask the package manager to perform a dex-opt with the given compiler filter.
*
@@ -500,6 +501,16 @@
String targetCompilerFilter, boolean force);
/**
+ * Ask the package manager to perform a dex-opt with the given compiler filter on the
+ * secondary dex files belonging to the given package.
+ *
+ * Note: exposed only for the shell command to allow moving packages explicitly to a
+ * definite state.
+ */
+ boolean performDexOptSecondary(String packageName,
+ String targetCompilerFilter, boolean force);
+
+ /**
* Ask the package manager to dump profiles associated with a package.
*/
void dumpProfiles(String packageName);
@@ -507,6 +518,18 @@
void forceDexOpt(String packageName);
/**
+ * Execute the background dexopt job immediately.
+ */
+ boolean runBackgroundDexoptJob();
+
+ /**
+ * Reconcile the information we have about the secondary dex files belonging to
+ * {@code packagName} and the actual dex files. For all dex files that were
+ * deleted, update the internal records and delete the generated oat files.
+ */
+ void reconcileSecondaryDexFiles(String packageName);
+
+ /**
* Update status of external media on the package manager to scan and
* install packages installed on the external media. Like say the
* StorageManagerService uses this to call into the package manager to update
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f1bffd3..f7ebf99a 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4128,6 +4128,7 @@
EPHEMERAL_SETTINGS.add(FONT_SCALE);
EPHEMERAL_SETTINGS.add(HAPTIC_FEEDBACK_ENABLED);
EPHEMERAL_SETTINGS.add(TIME_12_24);
+ EPHEMERAL_SETTINGS.add(SOUND_EFFECTS_ENABLED);
}
/**
diff --git a/core/java/android/text/ITextClassificationService.aidl b/core/java/android/text/ITextClassificationService.aidl
new file mode 100644
index 0000000..a73dbf0
--- /dev/null
+++ b/core/java/android/text/ITextClassificationService.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Interface to the text classification service, which grants access to the text classification
+ * LSTM model file.
+ * {@hide}
+ */
+interface ITextClassificationService {
+
+ /**
+ * Request a file descriptor with read-only access to the LSTM model file.
+ * This file descriptor should be closed after the client is done with it.
+ */
+ ParcelFileDescriptor getModelFileFd();
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e8535cdb..597c051 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -24768,6 +24768,13 @@
}
/**
+ * @hide Binary compatibility stub. To be removed when we finalize O APIs.
+ */
+ public void setTooltip(@Nullable CharSequence tooltipText) {
+ setTooltipText(tooltipText);
+ }
+
+ /**
* Returns the view's tooltip text.
*
* @return the tooltip text
@@ -24777,6 +24784,14 @@
return mTooltipInfo != null ? mTooltipInfo.mTooltipText : null;
}
+ /**
+ * @hide Binary compatibility stub. To be removed when we finalize O APIs.
+ */
+ @Nullable
+ public CharSequence getTooltip() {
+ return getTooltipText();
+ }
+
private boolean showTooltip(int x, int y, boolean fromLongClick) {
if (mAttachInfo == null) {
return false;
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 46324a3..5199b26 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -232,7 +232,7 @@
mDecorView.getLayoutParams();
updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
- p.width, p.height, mAnchoredGravity));
+ p.width, p.height, mAnchoredGravity, false));
update(p.x, p.y, -1, -1, true);
}
}
@@ -1237,7 +1237,7 @@
preparePopup(p);
final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
- p.width, p.height, gravity);
+ p.width, p.height, gravity, mAllowScrollingAnchorParent);
updateAboveAnchor(aboveAnchor);
p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1;
@@ -1529,10 +1529,12 @@
* @param xOffset absolute horizontal offset from the left of the anchor
* @param yOffset absolute vertical offset from the top of the anchor
* @param gravity horizontal gravity specifying popup alignment
+ * @param allowScroll whether the anchor view's parent may be scrolled
+ * when the popup window doesn't fit on screen
* @return true if the popup is translated upwards to fit on screen
*/
private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams outParams,
- int xOffset, int yOffset, int width, int height, int gravity) {
+ int xOffset, int yOffset, int width, int height, int gravity, boolean allowScroll) {
final int anchorHeight = anchor.getHeight();
final int anchorWidth = anchor.getWidth();
if (mOverlapAnchor) {
@@ -1586,7 +1588,7 @@
final int scrollY = anchor.getScrollY();
final Rect r = new Rect(scrollX, scrollY, scrollX + width + xOffset,
scrollY + height + anchorHeight + yOffset);
- if (mAllowScrollingAnchorParent && anchor.requestRectangleOnScreen(r, true)) {
+ if (allowScroll && anchor.requestRectangleOnScreen(r, true)) {
// Reset for the new anchor position.
anchor.getLocationInWindow(drawingLocation);
outParams.x = drawingLocation[0] + xOffset;
@@ -2182,15 +2184,19 @@
}
final boolean aboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
- width, height, gravity);
+ width, height, gravity, mAllowScrollingAnchorParent);
updateAboveAnchor(aboveAnchor);
final boolean paramsChanged = oldGravity != p.gravity || oldX != p.x || oldY != p.y
|| oldWidth != p.width || oldHeight != p.height;
- // If width and mWidth were both < 0 then we have a MATCH_PARENT/WRAP_CONTENT case.
- // findDropDownPosition will have resolved this to absolute values,
- // but we don't want to update mWidth/mHeight to these absolute values.
- update(p.x, p.y, width < 0 ? width : p.width, height < 0 ? height : p.height, paramsChanged);
+
+ // If width and mWidth were both < 0 then we have a MATCH_PARENT or
+ // WRAP_CONTENT case. findDropDownPosition will have resolved this to
+ // absolute values, but we don't want to update mWidth/mHeight to these
+ // absolute values.
+ final int newWidth = width < 0 ? width : p.width;
+ final int newHeight = height < 0 ? height : p.height;
+ update(p.x, p.y, newWidth, newHeight, paramsChanged);
}
/**
diff --git a/core/res/res/raw/accessibility_gestures.bin b/core/res/res/raw/accessibility_gestures.bin
deleted file mode 100644
index acd7993..0000000
--- a/core/res/res/raw/accessibility_gestures.bin
+++ /dev/null
Binary files differ
diff --git a/core/res/res/values-mcc208-mnc10/config.xml b/core/res/res/values-mcc208-mnc10/config.xml
index d3640e5..3ed7818 100644
--- a/core/res/res/values-mcc208-mnc10/config.xml
+++ b/core/res/res/values-mcc208-mnc10/config.xml
@@ -31,28 +31,4 @@
<item>[ApnSettingV3]INTERNET NRJ,internetnrj,,,,,,,,,208,10,,DUN,,,true,0,,,,,,,gid,4E</item>
</string-array>
- <string-array translatable="false" name="config_operatorConsideredNonRoaming">
- <item>21401</item>
- <item>21402</item>
- <item>21403</item>
- <item>21404</item>
- <item>21405</item>
- <item>21406</item>
- <item>21407</item>
- <item>21408</item>
- <item>21409</item>
- <item>21410</item>
- <item>21411</item>
- <item>21412</item>
- <item>21413</item>
- <item>21414</item>
- <item>21415</item>
- <item>21416</item>
- <item>21417</item>
- <item>21418</item>
- <item>21419</item>
- <item>21420</item>
- <item>21421</item>
- </string-array>
-
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 554e123..25ebb87 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1453,7 +1453,6 @@
<java-symbol type="raw" name="color_fade_vert" />
<java-symbol type="raw" name="color_fade_frag" />
- <java-symbol type="raw" name="accessibility_gestures" />
<java-symbol type="raw" name="loaderror" />
<java-symbol type="raw" name="nodomain" />
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
index 115c622..2fb6843 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -135,6 +135,10 @@
mAdapter.setDiscoverableTimeout(timeout);
}
+ public long getDiscoveryEndMillis() {
+ return mAdapter.getDiscoveryEndMillis();
+ }
+
public void setName(String name) {
mAdapter.setName(name);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
index 582b19b..b95d2e6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
@@ -16,19 +16,18 @@
package com.android.server.accessibility;
+import android.accessibilityservice.AccessibilityService;
import android.content.Context;
import android.gesture.Gesture;
-import android.gesture.GestureLibraries;
-import android.gesture.GestureLibrary;
import android.gesture.GesturePoint;
import android.gesture.GestureStore;
import android.gesture.GestureStroke;
import android.gesture.Prediction;
+import android.graphics.PointF;
import android.util.Slog;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.MotionEvent;
-import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import com.android.internal.R;
@@ -47,6 +46,49 @@
// Tag for logging received events.
private static final String LOG_TAG = "AccessibilityGestureDetector";
+ // Constants for sampling motion event points.
+ // We sample based on a minimum distance between points, primarily to improve accuracy by
+ // reducing noisy minor changes in direction.
+ private static final float MIN_INCHES_BETWEEN_SAMPLES = 0.1f;
+ private final float mMinPixelsBetweenSamplesX;
+ private final float mMinPixelsBetweenSamplesY;
+
+ // Constants for separating gesture segments
+ private static final float ANGLE_THRESHOLD = 0.0f;
+
+ // Constants for line segment directions
+ private static final int LEFT = 0;
+ private static final int RIGHT = 1;
+ private static final int UP = 2;
+ private static final int DOWN = 3;
+ private static final int[][] DIRECTIONS_TO_GESTURE_ID = {
+ {
+ AccessibilityService.GESTURE_SWIPE_LEFT,
+ AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT,
+ AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP,
+ AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN
+ },
+ {
+ AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT,
+ AccessibilityService.GESTURE_SWIPE_RIGHT,
+ AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP,
+ AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN
+ },
+ {
+ AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT,
+ AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT,
+ AccessibilityService.GESTURE_SWIPE_UP,
+ AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN
+ },
+ {
+ AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT,
+ AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT,
+ AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP,
+ AccessibilityService.GESTURE_SWIPE_DOWN
+ }
+ };
+
+
/**
* Listener functions are called as a result of onMoveEvent(). The current
* MotionEvent in the context of these functions is the event passed into
@@ -102,10 +144,8 @@
}
private final Listener mListener;
- private final GestureDetector mGestureDetector;
-
- // The library for gesture detection.
- private final GestureLibrary mGestureLibrary;
+ private final Context mContext; // Retained for on-demand construction of GestureDetector.
+ protected GestureDetector mGestureDetector; // Double-tap detector. Visible for test.
// Indicates that a single tap has occurred.
private boolean mFirstTapDetected;
@@ -168,28 +208,26 @@
// movement when gesturing, and touch exploring. Based on user testing,
// all gestures started with the initial movement taking less than 100ms.
// When touch exploring, the first movement almost always takes longer than
- // 200ms. From this data, 200ms seems the best value to decide what
- // kind of interaction it is.
- private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 200;
+ // 200ms.
+ private static final long CANCEL_ON_PAUSE_THRESHOLD_NOT_STARTED_MS = 150;
// Time threshold used to determine if a gesture should be cancelled. If
- // the finger pauses for longer than this delay, the ongoing gesture is
+ // the finger takes more than this time to move 1cm, the ongoing gesture is
// cancelled.
- private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 500;
+ private static final long CANCEL_ON_PAUSE_THRESHOLD_STARTED_MS = 300;
AccessibilityGestureDetector(Context context, Listener listener) {
mListener = listener;
-
- mGestureDetector = new GestureDetector(context, this);
- mGestureDetector.setOnDoubleTapListener(this);
-
- mGestureLibrary = GestureLibraries.fromRawResource(context, R.raw.accessibility_gestures);
- mGestureLibrary.setOrientationStyle(8 /* GestureStore.ORIENTATION_SENSITIVE_8 */);
- mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE);
- mGestureLibrary.load();
+ mContext = context;
mGestureDetectionThreshold = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1,
context.getResources().getDisplayMetrics()) * GESTURE_CONFIRM_MM;
+
+ // Calculate minimum gesture velocity
+ final float pixelsPerInchX = context.getResources().getDisplayMetrics().xdpi;
+ final float pixelsPerInchY = context.getResources().getDisplayMetrics().ydpi;
+ mMinPixelsBetweenSamplesX = MIN_INCHES_BETWEEN_SAMPLES * pixelsPerInchX;
+ mMinPixelsBetweenSamplesY = MIN_INCHES_BETWEEN_SAMPLES * pixelsPerInchY;
}
/**
@@ -205,6 +243,18 @@
* @return true if the event is consumed, else false
*/
public boolean onMotionEvent(MotionEvent event, int policyFlags) {
+
+ // Construct GestureDetector double-tap detector on demand, so that testable sub-class
+ // can use mock GestureDetector.
+ // TODO: Break the circular dependency between GestureDetector's constructor and
+ // AccessibilityGestureDetector's constructor. Construct GestureDetector in TouchExplorer,
+ // using a GestureDetector listener owned by TouchExplorer, which passes double-tap state
+ // information to AccessibilityGestureDetector.
+ if (mGestureDetector == null) {
+ mGestureDetector = new GestureDetector(mContext, this);
+ mGestureDetector.setOnDoubleTapListener(this);
+ }
+
final float x = event.getX();
final float y = event.getY();
final long time = event.getEventTime();
@@ -267,7 +317,7 @@
final float dX = Math.abs(x - mPreviousGestureX);
final float dY = Math.abs(y - mPreviousGestureY);
- if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) {
+ if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
mPreviousGestureX = x;
mPreviousGestureY = y;
mStrokeBuffer.add(new GesturePoint(x, y, time));
@@ -280,8 +330,11 @@
return finishDoubleTap(event, policyFlags);
}
if (mGestureStarted) {
- mStrokeBuffer.add(new GesturePoint(x, y, time));
-
+ final float dX = Math.abs(x - mPreviousGestureX);
+ final float dY = Math.abs(y - mPreviousGestureY);
+ if (dX >= mMinPixelsBetweenSamplesX || dY >= mMinPixelsBetweenSamplesY) {
+ mStrokeBuffer.add(new GesturePoint(x, y, time));
+ }
return recognizeGesture(event, policyFlags);
}
break;
@@ -397,30 +450,154 @@
mStrokeBuffer.clear();
}
+ /**
+ * Looks at the sequence of motions in mStrokeBuffer, classifies the gesture, then calls
+ * Listener callbacks for success or failure.
+ *
+ * @param event The raw motion event to pass to the listener callbacks.
+ * @param policyFlags Policy flags for the event.
+ *
+ * @return true if the event is consumed, else false
+ */
private boolean recognizeGesture(MotionEvent event, int policyFlags) {
- Gesture gesture = new Gesture();
- gesture.addStroke(new GestureStroke(mStrokeBuffer));
-
- ArrayList<Prediction> predictions = mGestureLibrary.recognize(gesture);
- if (!predictions.isEmpty()) {
- Prediction bestPrediction = predictions.get(0);
- if (bestPrediction.score >= MIN_PREDICTION_SCORE) {
- if (DEBUG) {
- Slog.i(LOG_TAG, "gesture: " + bestPrediction.name + " score: "
- + bestPrediction.score);
- }
- try {
- final int gestureId = Integer.parseInt(bestPrediction.name);
- return mListener.onGestureCompleted(gestureId);
- } catch (NumberFormatException nfe) {
- Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name);
- }
- }
+ if (mStrokeBuffer.size() < 2) {
+ return mListener.onGestureCancelled(event, policyFlags);
}
+ // Look at mStrokeBuffer and extract 2 line segments, delimited by near-perpendicular
+ // direction change.
+ // Method: for each sampled motion event, check the angle of the most recent motion vector
+ // versus the preceding motion vector, and segment the line if the angle is about
+ // 90 degrees.
+
+ ArrayList<PointF> path = new ArrayList<>();
+ PointF lastDelimiter = new PointF(mStrokeBuffer.get(0).x, mStrokeBuffer.get(0).y);
+ path.add(lastDelimiter);
+
+ float dX = 0; // Sum of unit vectors from last delimiter to each following point
+ float dY = 0;
+ int count = 0; // Number of points since last delimiter
+ float length = 0; // Vector length from delimiter to most recent point
+
+ PointF next = new PointF();
+ for (int i = 1; i < mStrokeBuffer.size(); ++i) {
+ next = new PointF(mStrokeBuffer.get(i).x, mStrokeBuffer.get(i).y);
+ if (count > 0) {
+ // Average of unit vectors from delimiter to following points
+ float currentDX = dX / count;
+ float currentDY = dY / count;
+
+ // newDelimiter is a possible new delimiter, based on a vector with length from
+ // the last delimiter to the previous point, but in the direction of the average
+ // unit vector from delimiter to previous points.
+ // Using the averaged vector has the effect of "squaring off the curve",
+ // creating a sharper angle between the last motion and the preceding motion from
+ // the delimiter. In turn, this sharper angle achieves the splitting threshold
+ // even in a gentle curve.
+ PointF newDelimiter = new PointF(length * currentDX + lastDelimiter.x,
+ length * currentDY + lastDelimiter.y);
+
+ // Unit vector from newDelimiter to the most recent point
+ float nextDX = next.x - newDelimiter.x;
+ float nextDY = next.y - newDelimiter.y;
+ float nextLength = (float) Math.sqrt(nextDX * nextDX + nextDY * nextDY);
+ nextDX = nextDX / nextLength;
+ nextDY = nextDY / nextLength;
+
+ // Compare the initial motion direction to the most recent motion direction,
+ // and segment the line if direction has changed by about 90 degrees.
+ float dot = currentDX * nextDX + currentDY * nextDY;
+ if (dot < ANGLE_THRESHOLD) {
+ path.add(newDelimiter);
+ lastDelimiter = newDelimiter;
+ dX = 0;
+ dY = 0;
+ count = 0;
+ }
+ }
+
+ // Vector from last delimiter to most recent point
+ float currentDX = next.x - lastDelimiter.x;
+ float currentDY = next.y - lastDelimiter.y;
+ length = (float) Math.sqrt(currentDX * currentDX + currentDY * currentDY);
+
+ // Increment sum of unit vectors from delimiter to each following point
+ count = count + 1;
+ dX = dX + currentDX / length;
+ dY = dY + currentDY / length;
+ }
+
+ path.add(next);
+ Slog.i(LOG_TAG, "path=" + path.toString());
+
+ // Classify line segments, and call Listener callbacks.
+ return recognizeGesturePath(event, policyFlags, path);
+ }
+
+ /**
+ * Classifies a pair of line segments, by direction.
+ * Calls Listener callbacks for success or failure.
+ *
+ * @param event The raw motion event to pass to the listener's onGestureCanceled method.
+ * @param policyFlags Policy flags for the event.
+ * @param path A sequence of motion line segments derived from motion points in mStrokeBuffer.
+ *
+ * @return true if the event is consumed, else false
+ */
+ private boolean recognizeGesturePath(MotionEvent event, int policyFlags,
+ ArrayList<PointF> path) {
+
+ if (path.size() == 2) {
+ PointF start = path.get(0);
+ PointF end = path.get(1);
+
+ float dX = end.x - start.x;
+ float dY = end.y - start.y;
+ int direction = toDirection(dX, dY);
+ switch (direction) {
+ case LEFT:
+ return mListener.onGestureCompleted(AccessibilityService.GESTURE_SWIPE_LEFT);
+ case RIGHT:
+ return mListener.onGestureCompleted(AccessibilityService.GESTURE_SWIPE_RIGHT);
+ case UP:
+ return mListener.onGestureCompleted(AccessibilityService.GESTURE_SWIPE_UP);
+ case DOWN:
+ return mListener.onGestureCompleted(AccessibilityService.GESTURE_SWIPE_DOWN);
+ default:
+ // Do nothing.
+ }
+
+ } else if (path.size() == 3) {
+ PointF start = path.get(0);
+ PointF mid = path.get(1);
+ PointF end = path.get(2);
+
+ float dX0 = mid.x - start.x;
+ float dY0 = mid.y - start.y;
+
+ float dX1 = end.x - mid.x;
+ float dY1 = end.y - mid.y;
+
+ int segmentDirection0 = toDirection(dX0, dY0);
+ int segmentDirection1 = toDirection(dX1, dY1);
+ int gestureId = DIRECTIONS_TO_GESTURE_ID[segmentDirection0][segmentDirection1];
+ return mListener.onGestureCompleted(gestureId);
+ }
+ // else if (path.size() < 2 || 3 < path.size()) then no gesture recognized.
return mListener.onGestureCancelled(event, policyFlags);
}
+ /** Maps a vector to a dominant direction in set {LEFT, RIGHT, UP, DOWN}. */
+ private static int toDirection(float dX, float dY) {
+ if (Math.abs(dX) > Math.abs(dY)) {
+ // Horizontal
+ return (dX < 0) ? LEFT : RIGHT;
+ } else {
+ // Vertical
+ return (dY < 0) ? UP : DOWN;
+ }
+ }
+
private MotionEvent mapSecondPointerToFirstPointer(MotionEvent event) {
// Only map basic events when two fingers are down.
if (event.getPointerCount() != 2 ||
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 5739693..ed2da68 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -205,18 +205,26 @@
private AudioAttributes calculateAttributes() {
final Notification n = sbn.getNotification();
- AudioAttributes attributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
+ AudioAttributes attributes = getChannel().getAudioAttributes();
+ if (attributes == null) {
+ attributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
+ }
- if (n.audioAttributes != null) {
- // prefer audio attributes to stream type
- attributes = n.audioAttributes;
- } else if (n.audioStreamType >= 0 && n.audioStreamType < AudioSystem.getNumStreamTypes()) {
- // the stream type is valid, use it
- attributes = new AudioAttributes.Builder()
- .setInternalLegacyStreamType(n.audioStreamType)
- .build();
- } else if (n.audioStreamType != AudioSystem.STREAM_DEFAULT) {
- Log.w(TAG, String.format("Invalid stream type: %d", n.audioStreamType));
+ if (mPreChannelsNotification
+ && (getChannel().getUserLockedFields()
+ & NotificationChannel.USER_LOCKED_SOUND) == 0) {
+ if (n.audioAttributes != null) {
+ // prefer audio attributes to stream type
+ attributes = n.audioAttributes;
+ } else if (n.audioStreamType >= 0
+ && n.audioStreamType < AudioSystem.getNumStreamTypes()) {
+ // the stream type is valid, use it
+ attributes = new AudioAttributes.Builder()
+ .setInternalLegacyStreamType(n.audioStreamType)
+ .build();
+ } else if (n.audioStreamType != AudioSystem.STREAM_DEFAULT) {
+ Log.w(TAG, String.format("Invalid stream type: %d", n.audioStreamType));
+ }
}
return attributes;
}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 5b6ac69..8176e5d 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -569,7 +569,7 @@
channel.setBypassDnd(updatedChannel.canBypassDnd());
}
if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_SOUND) == 0) {
- channel.setSound(updatedChannel.getSound());
+ channel.setSound(updatedChannel.getSound(), updatedChannel.getAudioAttributes());
}
if ((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_VIBRATION) == 0) {
channel.enableVibration(updatedChannel.shouldVibrate());
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 601a219..66977d6 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -30,10 +30,13 @@
import android.os.BatteryManager;
import android.os.Environment;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.storage.StorageManager;
import android.util.ArraySet;
import android.util.Log;
+import com.android.server.pm.dex.DexManager;
+
import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.TimeUnit;
@@ -59,21 +62,33 @@
"android",
BackgroundDexOptService.class.getName());
+ // Possible return codes of individual optimization steps.
+
+ // Optimizations finished. All packages were processed.
+ private static final int OPTIMIZE_PROCESSED = 0;
+ // Optimizations should continue. Issued after checking the scheduler, disk space or battery.
+ private static final int OPTIMIZE_CONTINUE = 1;
+ // Optimizations should be aborted. Job scheduler requested it.
+ private static final int OPTIMIZE_ABORT_BY_JOB_SCHEDULER = 2;
+ // Optimizations should be aborted. No space left on device.
+ private static final int OPTIMIZE_ABORT_NO_SPACE_LEFT = 3;
+
/**
* Set of failed packages remembered across job runs.
*/
- static final ArraySet<String> sFailedPackageNames = new ArraySet<String>();
+ static final ArraySet<String> sFailedPackageNamesPrimary = new ArraySet<String>();
+ static final ArraySet<String> sFailedPackageNamesSecondary = new ArraySet<String>();
/**
* Atomics set to true if the JobScheduler requests an abort.
*/
- final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false);
- final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false);
+ private final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false);
+ private final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false);
/**
* Atomic set to true if one job should exit early because another job was started.
*/
- final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
+ private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
private final File mDataDir = Environment.getDataDirectory();
@@ -104,8 +119,11 @@
// The idle maintanance job skips packages which previously failed to
// compile. The given package has changed and may successfully compile
// now. Remove it from the list of known failing packages.
- synchronized (sFailedPackageNames) {
- sFailedPackageNames.remove(packageName);
+ synchronized (sFailedPackageNamesPrimary) {
+ sFailedPackageNamesPrimary.remove(packageName);
+ }
+ synchronized (sFailedPackageNamesSecondary) {
+ sFailedPackageNamesSecondary.remove(packageName);
}
}
@@ -124,9 +142,9 @@
return (100 * level / scale);
}
- private long getLowStorageThreshold() {
+ private long getLowStorageThreshold(Context context) {
@SuppressWarnings("deprecation")
- final long lowThreshold = StorageManager.from(this).getStorageLowBytes(mDataDir);
+ final long lowThreshold = StorageManager.from(context).getStorageLowBytes(mDataDir);
if (lowThreshold == 0) {
Log.e(TAG, "Invalid low storage threshold");
}
@@ -155,7 +173,7 @@
// Load low battery threshold from the system config. This is a 0-100 integer.
final int lowBatteryThreshold = getResources().getInteger(
com.android.internal.R.integer.config_lowBatteryWarningLevel);
- final long lowThreshold = getLowStorageThreshold();
+ final long lowThreshold = getLowStorageThreshold(this);
mAbortPostBootUpdate.set(false);
@@ -206,61 +224,123 @@
new Thread("BackgroundDexOptService_IdleOptimization") {
@Override
public void run() {
- idleOptimization(jobParams, pm, pkgs);
+ int result = idleOptimization(pm, pkgs, BackgroundDexOptService.this);
+ if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+ Log.w(TAG, "Idle optimizations aborted because of space constraints.");
+ // If we didn't abort we ran to completion (or stopped because of space).
+ // Abandon our timeslice and do not reschedule.
+ jobFinished(jobParams, /* reschedule */ false);
+ }
}
}.start();
return true;
}
- private void idleOptimization(JobParameters jobParams, PackageManagerService pm,
- ArraySet<String> pkgs) {
+ // Optimize the given packages and return the optimization result (one of the OPTIMIZE_* codes).
+ private int idleOptimization(PackageManagerService pm, ArraySet<String> pkgs, Context context) {
Log.i(TAG, "Performing idle optimizations");
// If post-boot update is still running, request that it exits early.
mExitPostBootUpdate.set(true);
-
mAbortIdleOptimization.set(false);
- final long lowThreshold = getLowStorageThreshold();
- for (String pkg : pkgs) {
- if (mAbortIdleOptimization.get()) {
- // JobScheduler requested an early abort.
- return;
+ long lowStorageThreshold = getLowStorageThreshold(context);
+ // Optimize primary apks.
+ int result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ true,
+ sFailedPackageNamesPrimary);
+
+ if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+ return result;
+ }
+
+ if (SystemProperties.getBoolean("dalvik.vm.deopt.secondary", false)) {
+ result = reconcileSecondaryDexFiles(pm.getDexManager());
+ if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+ return result;
}
- synchronized (sFailedPackageNames) {
- if (sFailedPackageNames.contains(pkg)) {
+ result = optimizePackages(pm, pkgs, lowStorageThreshold, /*is_for_primary_dex*/ false,
+ sFailedPackageNamesSecondary);
+ }
+ return result;
+ }
+
+ private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs,
+ long lowStorageThreshold, boolean is_for_primary_dex,
+ ArraySet<String> failedPackageNames) {
+ for (String pkg : pkgs) {
+ int abort_code = abortIdleOptimizations(lowStorageThreshold);
+ if (abort_code != OPTIMIZE_CONTINUE) {
+ return abort_code;
+ }
+
+ synchronized (failedPackageNames) {
+ if (failedPackageNames.contains(pkg)) {
// Skip previously failing package
continue;
+ } else {
+ // Conservatively add package to the list of failing ones in case performDexOpt
+ // never returns.
+ failedPackageNames.add(pkg);
}
}
- long usableSpace = mDataDir.getUsableSpace();
- if (usableSpace < lowThreshold) {
- // Rather bail than completely fill up the disk.
- Log.w(TAG, "Aborting background dex opt job due to low storage: " +
- usableSpace);
- break;
- }
-
- // Conservatively add package to the list of failing ones in case performDexOpt
- // never returns.
- synchronized (sFailedPackageNames) {
- sFailedPackageNames.add(pkg);
- }
// Optimize package if needed. Note that there can be no race between
// concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
- if (pm.performDexOpt(pkg,
- /* checkProfiles */ true,
- PackageManagerService.REASON_BACKGROUND_DEXOPT,
- /* force */ false)) {
+ boolean success = is_for_primary_dex
+ ? pm.performDexOpt(pkg,
+ /* checkProfiles */ true,
+ PackageManagerService.REASON_BACKGROUND_DEXOPT,
+ /* force */ false)
+ : pm.performDexOptSecondary(pkg,
+ PackageManagerServiceCompilerMapping.getFullCompilerFilter(),
+ /* force */ true);
+ if (success) {
// Dexopt succeeded, remove package from the list of failing ones.
- synchronized (sFailedPackageNames) {
- sFailedPackageNames.remove(pkg);
+ synchronized (failedPackageNames) {
+ failedPackageNames.remove(pkg);
}
}
}
- // Ran to completion, so we abandon our timeslice and do not reschedule.
- jobFinished(jobParams, /* reschedule */ false);
+ return OPTIMIZE_PROCESSED;
+ }
+
+ private int reconcileSecondaryDexFiles(DexManager dm) {
+ // TODO(calin): should we blacklist packages for which we fail to reconcile?
+ for (String p : dm.getAllPackagesWithSecondaryDexFiles()) {
+ if (mAbortIdleOptimization.get()) {
+ return OPTIMIZE_ABORT_BY_JOB_SCHEDULER;
+ }
+ dm.reconcileSecondaryDexFiles(p);
+ }
+ return OPTIMIZE_PROCESSED;
+ }
+
+ // Evaluate whether or not idle optimizations should continue.
+ private int abortIdleOptimizations(long lowStorageThreshold) {
+ if (mAbortIdleOptimization.get()) {
+ // JobScheduler requested an early abort.
+ return OPTIMIZE_ABORT_BY_JOB_SCHEDULER;
+ }
+ long usableSpace = mDataDir.getUsableSpace();
+ if (usableSpace < lowStorageThreshold) {
+ // Rather bail than completely fill up the disk.
+ Log.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace);
+ return OPTIMIZE_ABORT_NO_SPACE_LEFT;
+ }
+
+ return OPTIMIZE_CONTINUE;
+ }
+
+ /**
+ * Execute the idle optimizations immediately.
+ */
+ public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context) {
+ // Create a new object to make sure we don't interfere with the scheduled jobs.
+ // Note that this may still run at the same time with the job scheduled by the
+ // JobScheduler but the scheduler will not be able to cancel it.
+ BackgroundDexOptService bdos = new BackgroundDexOptService();
+ int result = bdos.idleOptimization(pm, pm.getOptimizablePackages(), context);
+ return result == OPTIMIZE_PROCESSED;
}
@Override
@@ -281,7 +361,7 @@
}
final ArraySet<String> pkgs = pm.getOptimizablePackages();
- if (pkgs == null || pkgs.isEmpty()) {
+ if (pkgs.isEmpty()) {
if (DEBUG_DEXOPT) {
Log.i(TAG, "No packages to optimize");
}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index fc66bb3..449d808 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -50,6 +50,14 @@
public static final int DEXOPT_BOOTCOMPLETE = 1 << 4;
/** Hint that the dexopt type is profile-guided. */
public static final int DEXOPT_PROFILE_GUIDED = 1 << 5;
+ /** The compilation is for a secondary dex file. */
+ public static final int DEXOPT_SECONDARY_DEX = 1 << 6;
+ /** Ignore the result of dexoptNeeded and force compilation. */
+ public static final int DEXOPT_FORCE = 1 << 7;
+ /** Indicates that the dex file passed to dexopt in on CE storage. */
+ public static final int DEXOPT_STORAGE_CE = 1 << 8;
+ /** Indicates that the dex file passed to dexopt in on DE storage. */
+ public static final int DEXOPT_STORAGE_DE = 1 << 9;
// NOTE: keep in sync with installd
public static final int FLAG_CLEAR_CACHE_ONLY = 1 << 8;
@@ -434,6 +442,20 @@
}
}
+ public boolean reconcileSecondaryDexFile(String apkPath, String packageName, int uid,
+ String[] isas, @Nullable String volumeUuid, int flags) throws InstallerException {
+ for (int i = 0; i < isas.length; i++) {
+ assertValidInstructionSet(isas[i]);
+ }
+ if (!checkBeforeRemote()) return false;
+ try {
+ return mInstalld.reconcileSecondaryDexFile(apkPath, packageName, uid, isas,
+ volumeUuid, flags);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
public void invalidateMounts() throws InstallerException {
if (!checkBeforeRemote()) return;
try {
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 8e201ac..db712ae 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -19,6 +19,7 @@
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageParser;
import android.os.Environment;
import android.os.PowerManager;
@@ -35,6 +36,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
import dalvik.system.DexFile;
@@ -43,6 +45,10 @@
import static com.android.server.pm.Installer.DEXOPT_PROFILE_GUIDED;
import static com.android.server.pm.Installer.DEXOPT_PUBLIC;
import static com.android.server.pm.Installer.DEXOPT_SAFEMODE;
+import static com.android.server.pm.Installer.DEXOPT_SECONDARY_DEX;
+import static com.android.server.pm.Installer.DEXOPT_FORCE;
+import static com.android.server.pm.Installer.DEXOPT_STORAGE_CE;
+import static com.android.server.pm.Installer.DEXOPT_STORAGE_DE;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
@@ -52,13 +58,13 @@
/**
* Helper class for running dexopt command on packages.
*/
-class PackageDexOptimizer {
+public class PackageDexOptimizer {
private static final String TAG = "PackageManager.DexOptimizer";
static final String OAT_DIR_NAME = "oat";
// TODO b/19550105 Remove error codes and use exceptions
- static final int DEX_OPT_SKIPPED = 0;
- static final int DEX_OPT_PERFORMED = 1;
- static final int DEX_OPT_FAILED = -1;
+ public static final int DEX_OPT_SKIPPED = 0;
+ public static final int DEX_OPT_PERFORMED = 1;
+ public static final int DEX_OPT_FAILED = -1;
private final Installer mInstaller;
private final Object mInstallLock;
@@ -100,6 +106,9 @@
return DEX_OPT_SKIPPED;
}
synchronized (mInstallLock) {
+ // During boot the system doesn't need to instantiate and obtain a wake lock.
+ // PowerManager might not be ready, but that doesn't mean that we can't proceed with
+ // dexopt.
final boolean useLock = mSystemReady;
if (useLock) {
mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
@@ -130,9 +139,11 @@
final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
- final String compilerFilter = getRealCompilerFilter(pkg, targetCompilerFilter);
+ final String compilerFilter = getRealCompilerFilter(pkg.applicationInfo,
+ targetCompilerFilter, isUsedByOtherApps(pkg));
final boolean profileUpdated = checkForProfileUpdates &&
isProfileUpdated(pkg, sharedGid, compilerFilter);
+
// TODO(calin,jeffhao): shared library paths should be adjusted to include previous code
// paths (b/34169257).
final String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries);
@@ -201,6 +212,79 @@
}
/**
+ * Performs dexopt on the secondary dex {@code path} belonging to the app {@code info}.
+ *
+ * @return
+ * DEX_OPT_FAILED if there was any exception during dexopt
+ * DEX_OPT_PERFORMED if dexopt was performed successfully on the given path.
+ * NOTE that DEX_OPT_PERFORMED for secondary dex files includes the case when the dex file
+ * didn't need an update. That's because at the moment we don't get more than success/failure
+ * from installd.
+ *
+ * TODO(calin): Consider adding return codes to installd dexopt invocation (rather than
+ * throwing exceptions). Or maybe make a separate call to installd to get DexOptNeeded, though
+ * that seems wasteful.
+ */
+ public int dexOptSecondaryDexPath(ApplicationInfo info, String path, Set<String> isas,
+ String compilerFilter, boolean isUsedByOtherApps) {
+ synchronized (mInstallLock) {
+ // During boot the system doesn't need to instantiate and obtain a wake lock.
+ // PowerManager might not be ready, but that doesn't mean that we can't proceed with
+ // dexopt.
+ final boolean useLock = mSystemReady;
+ if (useLock) {
+ mDexoptWakeLock.setWorkSource(new WorkSource(info.uid));
+ mDexoptWakeLock.acquire();
+ }
+ try {
+ return dexOptSecondaryDexPathLI(info, path, isas, compilerFilter,
+ isUsedByOtherApps);
+ } finally {
+ if (useLock) {
+ mDexoptWakeLock.release();
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mInstallLock")
+ private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path, Set<String> isas,
+ String compilerFilter, boolean isUsedByOtherApps) {
+ int dexoptFlags = getDexFlags(info, compilerFilter) | DEXOPT_SECONDARY_DEX;
+ // Check the app storage and add the appropriate flags.
+ if (info.dataDir.equals(info.deviceProtectedDataDir)) {
+ dexoptFlags |= DEXOPT_STORAGE_DE;
+ } else if (info.dataDir.equals(info.credentialProtectedDataDir)) {
+ dexoptFlags |= DEXOPT_STORAGE_CE;
+ } else {
+ Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName);
+ return DEX_OPT_FAILED;
+ }
+ compilerFilter = getRealCompilerFilter(info, compilerFilter, isUsedByOtherApps);
+ Log.d(TAG, "Running dexopt on: " + path
+ + " pkg=" + info.packageName + " isa=" + isas
+ + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
+ + " target-filter=" + compilerFilter);
+
+ try {
+ for (String isa : isas) {
+ // Reuse the same dexopt path as for the primary apks. We don't need all the
+ // arguments as some (dexopNeeded and oatDir) will be computed by installd because
+ // system server cannot read untrusted app content.
+ // TODO(calin): maybe add a separate call.
+ mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0,
+ /*oatDir*/ null, dexoptFlags,
+ compilerFilter, info.volumeUuid, /*sharedLibrariesPath*/ null);
+ }
+
+ return DEX_OPT_PERFORMED;
+ } catch (InstallerException e) {
+ Slog.w(TAG, "Failed to dexopt", e);
+ return DEX_OPT_FAILED;
+ }
+ }
+
+ /**
* Adjust the given dexopt-needed value. Can be overridden to influence the decision to
* optimize or not (and in what way).
*/
@@ -246,8 +330,9 @@
* The target filter will be updated if the package code is used by other apps
* or if it has the safe mode flag set.
*/
- private String getRealCompilerFilter(PackageParser.Package pkg, String targetCompilerFilter) {
- int flags = pkg.applicationInfo.flags;
+ private String getRealCompilerFilter(ApplicationInfo info, String targetCompilerFilter,
+ boolean isUsedByOtherApps) {
+ int flags = info.flags;
boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
if (vmSafeMode) {
// For the compilation, it doesn't really matter what we return here because installd
@@ -259,7 +344,7 @@
return getNonProfileGuidedCompilerFilter(targetCompilerFilter);
}
- if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps(pkg)) {
+ if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps) {
// If the dex files is used by other apps, we cannot use profile-guided compilation.
return getNonProfileGuidedCompilerFilter(targetCompilerFilter);
}
@@ -272,12 +357,16 @@
* filter.
*/
private int getDexFlags(PackageParser.Package pkg, String compilerFilter) {
- int flags = pkg.applicationInfo.flags;
+ return getDexFlags(pkg.applicationInfo, compilerFilter);
+ }
+
+ private int getDexFlags(ApplicationInfo info, String compilerFilter) {
+ int flags = info.flags;
boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
boolean debuggable = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
// Profile guide compiled oat files should not be public.
boolean isProfileGuidedFilter = isProfileGuidedCompilerFilter(compilerFilter);
- boolean isPublic = !pkg.isForwardLocked() && !isProfileGuidedFilter;
+ boolean isPublic = !info.isForwardLocked() && !isProfileGuidedFilter;
int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0;
int dexFlags =
(isPublic ? DEXOPT_PUBLIC : 0)
@@ -437,6 +526,19 @@
if ((flags & DEXOPT_SAFEMODE) == DEXOPT_SAFEMODE) {
flagsList.add("safemode");
}
+ if ((flags & DEXOPT_SECONDARY_DEX) == DEXOPT_SECONDARY_DEX) {
+ flagsList.add("secondary");
+ }
+ if ((flags & DEXOPT_FORCE) == DEXOPT_FORCE) {
+ flagsList.add("force");
+ }
+ if ((flags & DEXOPT_STORAGE_CE) == DEXOPT_STORAGE_CE) {
+ flagsList.add("storage_ce");
+ }
+ if ((flags & DEXOPT_STORAGE_DE) == DEXOPT_STORAGE_DE) {
+ flagsList.add("storage_de");
+ }
+
return String.join(",", flagsList);
}
@@ -461,5 +563,12 @@
// TODO: The return value is wrong when patchoat is needed.
return DexFile.DEX2OAT_FROM_SCRATCH;
}
+
+ @Override
+ protected int adjustDexoptFlags(int flags) {
+ // Add DEXOPT_FORCE flag to signal installd that it should force compilation
+ // and discard dexoptanalyzer result.
+ return flags | DEXOPT_FORCE;
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index b0512d4..4a426bd 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2227,7 +2227,7 @@
mInstaller = installer;
mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
"*dexopt*");
- mDexManager = new DexManager();
+ mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock);
mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
mOnPermissionChangeListeners = new OnPermissionChangeListeners(
@@ -8226,6 +8226,39 @@
targetCompilerFilter, getOrCreateCompilerPackageStats(p));
}
+ // Performs dexopt on the used secondary dex files belonging to the given package.
+ // Returns true if all dex files were process successfully (which could mean either dexopt or
+ // skip). Returns false if any of the files caused errors.
+ @Override
+ public boolean performDexOptSecondary(String packageName, String compilerFilter,
+ boolean force) {
+ return mDexManager.dexoptSecondaryDex(packageName, compilerFilter, force);
+ }
+
+ /**
+ * Reconcile the information we have about the secondary dex files belonging to
+ * {@code packagName} and the actual dex files. For all dex files that were
+ * deleted, update the internal records and delete the generated oat files.
+ */
+ @Override
+ public void reconcileSecondaryDexFiles(String packageName) {
+ mDexManager.reconcileSecondaryDexFiles(packageName);
+ }
+
+ // TODO(calin): this is only needed for BackgroundDexOptService. Find a cleaner way to inject
+ // a reference there.
+ /*package*/ DexManager getDexManager() {
+ return mDexManager;
+ }
+
+ /**
+ * Execute the background dexopt job immediately.
+ */
+ @Override
+ public boolean runBackgroundDexoptJob() {
+ return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext);
+ }
+
List<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) {
if (p.usesLibraries != null || p.usesOptionalLibraries != null
|| p.usesStaticLibraries != null) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 8a3f48e..9c9a671 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -23,7 +23,7 @@
/**
* Manage (retrieve) mappings from compilation reason to compilation filter.
*/
-class PackageManagerServiceCompilerMapping {
+public class PackageManagerServiceCompilerMapping {
// Names for compilation reasons.
static final String REASON_STRINGS[] = {
"first-boot", "boot", "install", "bg-dexopt", "ab-ota", "nsys-library", "shared-apk",
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 2f8d749..1203e4d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -118,6 +118,10 @@
return runInstallWrite();
case "compile":
return runCompile();
+ case "reconcile-secondary-dex-files":
+ return runreconcileSecondaryDexFiles();
+ case "bg-dexopt-job":
+ return runDexoptJob();
case "dump-profiles":
return runDumpProfiles();
case "list":
@@ -306,6 +310,7 @@
String compilerFilter = null;
String compilationReason = null;
String checkProfilesRaw = null;
+ boolean secondaryDex = false;
String opt;
while ((opt = getNextOption()) != null) {
@@ -333,6 +338,9 @@
clearProfileData = true;
compilationReason = "install";
break;
+ case "--secondary-dex":
+ secondaryDex = true;
+ break;
default:
pw.println("Error: Unknown option: " + opt);
return 1;
@@ -405,8 +413,11 @@
mInterface.clearApplicationProfileData(packageName);
}
- boolean result = mInterface.performDexOptMode(packageName,
- checkProfiles, targetCompilerFilter, forceCompilation);
+ boolean result = secondaryDex
+ ? mInterface.performDexOptSecondary(packageName,
+ targetCompilerFilter, forceCompilation)
+ : mInterface.performDexOptMode(packageName,
+ checkProfiles, targetCompilerFilter, forceCompilation);
if (!result) {
failedPackages.add(packageName);
}
@@ -434,6 +445,17 @@
}
}
+ private int runreconcileSecondaryDexFiles() throws RemoteException {
+ String packageName = getNextArg();
+ mInterface.reconcileSecondaryDexFiles(packageName);
+ return 0;
+ }
+
+ private int runDexoptJob() throws RemoteException {
+ boolean result = mInterface.runBackgroundDexoptJob();
+ return result ? 0 : -1;
+ }
+
private int runDumpProfiles() throws RemoteException {
String packageName = getNextArg();
mInterface.dumpProfiles(packageName);
@@ -1515,6 +1537,13 @@
}
pw.println(" --reset: restore package to its post-install state");
pw.println(" --check-prof (true | false): look at profiles when doing dexopt?");
+ pw.println(" --secondary-dex: compile app secondary dex files");
+ pw.println(" bg-dexopt-job");
+ pw.println(" Execute the background optimizations immediately.");
+ pw.println(" Note that the command only runs the background optimizer logic. It may");
+ pw.println(" overlap with the actual job but the job scheduler will not be able to");
+ pw.println(" cancel it. It will also run even if the device is not in the idle");
+ pw.println(" maintenance mode.");
pw.println(" list features");
pw.println(" Prints all features of the system.");
pw.println(" list instrumentation [-f] [TARGET-PACKAGE]");
@@ -1539,6 +1568,8 @@
pw.println(" -u: also include uninstalled packages");
pw.println(" --uid UID: filter to only show packages with the given UID");
pw.println(" --user USER_ID: only list packages belonging to the given user");
+ pw.println(" reconcile-secondary-dex-files TARGET-PACKAGE");
+ pw.println(" Reconciles the package secondary dex files with the generated oat files.");
pw.println(" list permission-groups");
pw.println(" Prints all known permission groups.");
pw.println(" list permissions [-g] [-f] [-d] [-u] [GROUP]");
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index e809213..00f3711 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -16,13 +16,21 @@
package com.android.server.pm.dex;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageParser;
-import android.content.pm.ApplicationInfo;
+import android.os.RemoteException;
+import android.os.storage.StorageManager;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.pm.Installer;
+import com.android.server.pm.Installer.InstallerException;
+import com.android.server.pm.PackageDexOptimizer;
import com.android.server.pm.PackageManagerServiceUtils;
+import com.android.server.pm.PackageManagerServiceCompilerMapping;
import java.io.File;
import java.io.IOException;
@@ -32,6 +40,9 @@
import java.util.Map;
import java.util.Set;
+import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
+import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
+
/**
* This class keeps track of how dex files are used.
* Every time it gets a notification about a dex file being loaded it tracks
@@ -54,15 +65,26 @@
// encode and save the dex usage data.
private final PackageDexUsage mPackageDexUsage;
+ private final IPackageManager mPackageManager;
+ private final PackageDexOptimizer mPackageDexOptimizer;
+ private final Object mInstallLock;
+ @GuardedBy("mInstallLock")
+ private final Installer mInstaller;
+
// Possible outcomes of a dex search.
private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found
private static int DEX_SEARCH_FOUND_PRIMARY = 1; // dex file is the primary/base apk
private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk
private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex
- public DexManager() {
+ public DexManager(IPackageManager pms, PackageDexOptimizer pdo,
+ Installer installer, Object installLock) {
mPackageCodeLocationsCache = new HashMap<>();
mPackageDexUsage = new PackageDexUsage();
+ mPackageManager = pms;
+ mPackageDexOptimizer = pdo;
+ mInstaller = installer;
+ mInstallLock = installLock;
}
/**
@@ -199,11 +221,144 @@
* Get the package dex usage for the given package name.
* @return the package data or null if there is no data available for this package.
*/
- public PackageDexUsage.PackageUseInfo getPackageUseInfo(String packageName) {
+ public PackageUseInfo getPackageUseInfo(String packageName) {
return mPackageDexUsage.getPackageUseInfo(packageName);
}
/**
+ * Perform dexopt on the package {@code packageName} secondary dex files.
+ * @return true if all secondary dex files were processed successfully (compiled or skipped
+ * because they don't need to be compiled)..
+ */
+ public boolean dexoptSecondaryDex(String packageName, String compilerFilter, boolean force) {
+ // Select the dex optimizer based on the force parameter.
+ // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust
+ // the necessary dexopt flags to make sure that compilation is not skipped. This avoid
+ // passing the force flag through the multitude of layers.
+ // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
+ // allocate an object here.
+ PackageDexOptimizer pdo = force
+ ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
+ : mPackageDexOptimizer;
+ PackageUseInfo useInfo = getPackageUseInfo(packageName);
+ if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
+ if (DEBUG) {
+ Slog.d(TAG, "No secondary dex use for package:" + packageName);
+ }
+ // Nothing to compile, return true.
+ return true;
+ }
+ boolean success = true;
+ for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
+ String dexPath = entry.getKey();
+ DexUseInfo dexUseInfo = entry.getValue();
+ PackageInfo pkg = null;
+ try {
+ pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
+ dexUseInfo.getOwnerUserId());
+ } catch (RemoteException e) {
+ throw new AssertionError(e);
+ }
+ // It may be that the package gets uninstalled while we try to compile its
+ // secondary dex files. If that's the case, just ignore.
+ // Note that we don't break the entire loop because the package might still be
+ // installed for other users.
+ if (pkg == null) {
+ Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
+ + " for user " + dexUseInfo.getOwnerUserId());
+ mPackageDexUsage.removeUserPackage(packageName, dexUseInfo.getOwnerUserId());
+ continue;
+ }
+ int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
+ dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps());
+ success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
+ }
+ return success;
+ }
+
+ /**
+ * Reconcile the information we have about the secondary dex files belonging to
+ * {@code packagName} and the actual dex files. For all dex files that were
+ * deleted, update the internal records and delete any generated oat files.
+ */
+ public void reconcileSecondaryDexFiles(String packageName) {
+ PackageUseInfo useInfo = getPackageUseInfo(packageName);
+ if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
+ if (DEBUG) {
+ Slog.d(TAG, "No secondary dex use for package:" + packageName);
+ }
+ // Nothing to reconcile.
+ return;
+ }
+ Set<String> dexFilesToRemove = new HashSet<>();
+ boolean updated = false;
+ for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
+ String dexPath = entry.getKey();
+ DexUseInfo dexUseInfo = entry.getValue();
+ PackageInfo pkg = null;
+ try {
+ // Note that we look for the package in the PackageManager just to be able
+ // to get back the real app uid and its storage kind. These are only used
+ // to perform extra validation in installd.
+ // TODO(calin): maybe a bit overkill.
+ pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
+ dexUseInfo.getOwnerUserId());
+ } catch (RemoteException ignore) {
+ // Can't happen, DexManager is local.
+ }
+ if (pkg == null) {
+ // It may be that the package was uninstalled while we process the secondary
+ // dex files.
+ Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName
+ + " for user " + dexUseInfo.getOwnerUserId());
+ // Update the usage and continue, another user might still have the package.
+ updated = mPackageDexUsage.removeUserPackage(
+ packageName, dexUseInfo.getOwnerUserId()) || updated;
+ continue;
+ }
+ ApplicationInfo info = pkg.applicationInfo;
+ int flags = 0;
+ if (info.dataDir.equals(info.deviceProtectedDataDir)) {
+ flags |= StorageManager.FLAG_STORAGE_DE;
+ } else if (info.dataDir.equals(info.credentialProtectedDataDir)) {
+ flags |= StorageManager.FLAG_STORAGE_CE;
+ } else {
+ Slog.e(TAG, "Could not infer CE/DE storage for package " + info.packageName);
+ updated = mPackageDexUsage.removeUserPackage(
+ packageName, dexUseInfo.getOwnerUserId()) || updated;
+ continue;
+ }
+
+ boolean dexStillExists = true;
+ synchronized(mInstallLock) {
+ try {
+ String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
+ dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
+ pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags);
+ } catch (InstallerException e) {
+ Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
+ " : " + e.getMessage());
+ }
+ }
+ if (!dexStillExists) {
+ updated = mPackageDexUsage.removeDexFile(
+ packageName, dexPath, dexUseInfo.getOwnerUserId()) || updated;
+ }
+
+ }
+ if (updated) {
+ mPackageDexUsage.maybeWriteAsync();
+ }
+ }
+
+ /**
+ * Return all packages that contain records of secondary dex files.
+ */
+ public Set<String> getAllPackagesWithSecondaryDexFiles() {
+ return mPackageDexUsage.getAllPackagesWithSecondaryDexFiles();
+ }
+
+ /**
* Retrieves the package which owns the given dexPath.
*/
private DexSearchResult getDexPackage(
diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
index 10384a2..3693bce0 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
@@ -376,12 +376,86 @@
}
}
+ /**
+ * Remove all the records about package {@code packageName} belonging to user {@code userId}.
+ * @return true if the record was found and actually deleted,
+ * false if the record doesn't exist
+ */
+ public boolean removeUserPackage(String packageName, int userId) {
+ synchronized (mPackageUseInfoMap) {
+ PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName);
+ if (packageUseInfo == null) {
+ return false;
+ }
+ boolean updated = false;
+ Iterator<Map.Entry<String, DexUseInfo>> dIt =
+ packageUseInfo.mDexUseInfoMap.entrySet().iterator();
+ while (dIt.hasNext()) {
+ DexUseInfo dexUseInfo = dIt.next().getValue();
+ if (dexUseInfo.mOwnerUserId == userId) {
+ dIt.remove();
+ updated = true;
+ }
+ }
+ return updated;
+ }
+ }
+
+ /**
+ * Remove the secondary dex file record belonging to the package {@code packageName}
+ * and user {@code userId}.
+ * @return true if the record was found and actually deleted,
+ * false if the record doesn't exist
+ */
+ public boolean removeDexFile(String packageName, String dexFile, int userId) {
+ synchronized (mPackageUseInfoMap) {
+ PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName);
+ if (packageUseInfo == null) {
+ return false;
+ }
+ return removeDexFile(packageUseInfo, dexFile, userId);
+ }
+ }
+
+ private boolean removeDexFile(PackageUseInfo packageUseInfo, String dexFile, int userId) {
+ DexUseInfo dexUseInfo = packageUseInfo.mDexUseInfoMap.get(dexFile);
+ if (dexUseInfo == null) {
+ return false;
+ }
+ if (dexUseInfo.mOwnerUserId == userId) {
+ packageUseInfo.mDexUseInfoMap.remove(dexFile);
+ return true;
+ }
+ return false;
+ }
+
public PackageUseInfo getPackageUseInfo(String packageName) {
synchronized (mPackageUseInfoMap) {
- return mPackageUseInfoMap.get(packageName);
+ PackageUseInfo useInfo = mPackageUseInfoMap.get(packageName);
+ // The useInfo contains a map for secondary dex files which could be modified
+ // concurrently after this method returns and thus outside the locking we do here.
+ // (i.e. the map is updated when new class loaders are created, which can happen anytime
+ // after this method returns)
+ // Make a defensive copy to be sure we don't get concurrent modifications.
+ return useInfo == null ? null : new PackageUseInfo(useInfo);
}
}
+ /**
+ * Return all packages that contain records of secondary dex files.
+ */
+ public Set<String> getAllPackagesWithSecondaryDexFiles() {
+ Set<String> packages = new HashSet<>();
+ synchronized (mPackageUseInfoMap) {
+ for (Map.Entry<String, PackageUseInfo> entry : mPackageUseInfoMap.entrySet()) {
+ if (!entry.getValue().mDexUseInfoMap.isEmpty()) {
+ packages.add(entry.getKey());
+ }
+ }
+ }
+ return packages;
+ }
+
public void clear() {
synchronized (mPackageUseInfoMap) {
mPackageUseInfoMap.clear();
diff --git a/services/core/java/com/android/server/text/TextClassificationService.java b/services/core/java/com/android/server/text/TextClassificationService.java
new file mode 100644
index 0000000..9358238
--- /dev/null
+++ b/services/core/java/com/android/server/text/TextClassificationService.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.text;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.text.ITextClassificationService;
+import android.util.Slog;
+
+import com.android.server.SystemService;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * Text classification service.
+ * This is used to provide access to the text classification LSTM model file.
+ */
+public class TextClassificationService extends ITextClassificationService.Stub {
+
+ private static final String LOG_TAG = "TextClassificationService";
+
+ public static final class Lifecycle extends SystemService {
+
+ private TextClassificationService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ mService = new TextClassificationService();
+ }
+
+ @Override
+ public void onStart() {
+ try {
+ publishBinderService(Context.TEXT_CLASSIFICATION_SERVICE, mService);
+ } catch (Throwable t) {
+ // Starting this service is not critical to the running of this device and should
+ // therefore not crash the device. If it fails, log the error and continue.
+ Slog.e(LOG_TAG, "Could not start the TextClassificationService.", t);
+ }
+ }
+ }
+
+ @Override
+ public synchronized ParcelFileDescriptor getModelFileFd() throws RemoteException {
+ try {
+ return ParcelFileDescriptor.open(
+ new File("/etc/assistant/smart-selection.model"),
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ } catch (Throwable t) {
+ Slog.e(LOG_TAG, "Error retrieving an fd to the text classification model file.", t);
+ throw new RemoteException(t.getMessage());
+ }
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c3ef23b..712441c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -100,6 +100,7 @@
import com.android.server.statusbar.StatusBarManagerService;
import com.android.server.storage.DeviceStorageMonitorService;
import com.android.server.telecom.TelecomLoaderService;
+import com.android.server.text.TextClassificationService;
import com.android.server.trust.TrustManagerService;
import com.android.server.tv.TvInputManagerService;
import com.android.server.tv.TvRemoteService;
@@ -951,6 +952,12 @@
traceEnd();
}
+ if (!disableNonCoreServices) {
+ traceBeginAndSlog("StartTextClassificationService");
+ mSystemServiceManager.startService(TextClassificationService.Lifecycle.class);
+ traceEnd();
+ }
+
if (!disableNetwork) {
traceBeginAndSlog("StartNetworkScoreService");
try {
diff --git a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 468a26b..b8655607 100644
--- a/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/notification/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -85,6 +85,10 @@
300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400,
300, 400, 300, 400, 300, 400, 300, 400, 300, 400, 300, 400 };
private static final Uri CUSTOM_SOUND = Settings.System.DEFAULT_ALARM_ALERT_URI;
+ private static final AudioAttributes CUSTOM_ATTRIBUTES = new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .build();
private static final int CUSTOM_LIGHT_COLOR = Color.BLACK;
private static final int CUSTOM_LIGHT_ON = 10000;
private static final int CUSTOM_LIGHT_OFF = 10000;
@@ -200,10 +204,11 @@
if (noisy) {
if (defaultSound) {
defaults |= Notification.DEFAULT_SOUND;
- channel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI);
+ channel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
+ Notification.AUDIO_ATTRIBUTES_DEFAULT);
} else {
builder.setSound(CUSTOM_SOUND);
- channel.setSound(CUSTOM_SOUND);
+ channel.setSound(CUSTOM_SOUND, CUSTOM_ATTRIBUTES);
}
}
if (buzzy) {
@@ -521,6 +526,8 @@
verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), eq(FALLBACK_VIBRATION),
eq(-1), (AudioAttributes) anyObject());
+ verify(mRingtonePlayer, never()).playAsync
+ (anyObject(), anyObject(), anyBoolean(), anyObject());
}
@Test
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
index 15dcc26..2ab1f30 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationRecordTest.java
@@ -118,6 +118,7 @@
defaults |= Notification.DEFAULT_SOUND;
} else {
builder.setSound(CUSTOM_SOUND, CUSTOM_ATTRIBUTES);
+ channel.setSound(CUSTOM_SOUND, CUSTOM_ATTRIBUTES);
}
}
if (buzzy) {
@@ -150,29 +151,31 @@
@Test
public void testSound_default_preUpgradeUsesNotification() throws Exception {
- defaultChannel.setSound(null);
+ defaultChannel.setSound(null, null);
// pre upgrade, default sound.
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, record.getSound());
+ assertEquals(Notification.AUDIO_ATTRIBUTES_DEFAULT, record.getAudioAttributes());
}
@Test
public void testSound_custom_preUpgradeUsesNotification() throws Exception {
- defaultChannel.setSound(null);
+ defaultChannel.setSound(null, null);
// pre upgrade, custom sound.
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(CUSTOM_SOUND, record.getSound());
+ assertEquals(CUSTOM_ATTRIBUTES, record.getAudioAttributes());
}
@Test
public void testSound_default_userLocked_preUpgrade() throws Exception {
- defaultChannel.setSound(CUSTOM_SOUND);
+ defaultChannel.setSound(CUSTOM_SOUND, CUSTOM_ATTRIBUTES);
defaultChannel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
// pre upgrade, default sound.
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
@@ -180,17 +183,19 @@
NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
assertEquals(CUSTOM_SOUND, record.getSound());
+ assertEquals(CUSTOM_ATTRIBUTES, record.getAudioAttributes());
}
@Test
public void testSound_default_upgradeUsesChannel() throws Exception {
- channel.setSound(CUSTOM_SOUND);
+ channel.setSound(CUSTOM_SOUND, CUSTOM_ATTRIBUTES);
// post upgrade, default sound.
StatusBarNotification sbn = getNotification(false /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
assertEquals(CUSTOM_SOUND, record.getSound());
+ assertEquals(CUSTOM_ATTRIBUTES, record.getAudioAttributes());
}
@Test
@@ -239,28 +244,6 @@
}
@Test
- public void testAudioAttributes_preUpgrade() throws Exception {
- defaultChannel.setSound(null);
- // pre upgrade, default sound.
- StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
- false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
-
- NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
- assertEquals(CUSTOM_ATTRIBUTES, record.getAudioAttributes());
- }
-
- @Test
- public void testAudioAttributes_upgrade() throws Exception {
- channel.setSound(null);
- // post upgrade, default sound.
- StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
- false /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
-
- NotificationRecord record = new NotificationRecord(mMockContext, sbn, defaultChannel);
- assertEquals(CUSTOM_ATTRIBUTES, record.getAudioAttributes());
- }
-
- @Test
public void testImportance_preUpgrade() throws Exception {
StatusBarNotification sbn = getNotification(true /*preO */, true /* noisy */,
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */);
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index b53ec45..9fa46d1 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -36,6 +36,7 @@
import android.app.NotificationManager;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.media.AudioAttributes;
import android.net.Uri;
import android.os.Build;
import android.os.UserHandle;
@@ -66,9 +67,12 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class RankingHelperTest {
- @Mock NotificationUsageStats mUsageStats;
- @Mock RankingHandler handler;
- @Mock PackageManager mPm;
+ @Mock
+ NotificationUsageStats mUsageStats;
+ @Mock
+ RankingHandler handler;
+ @Mock
+ PackageManager mPm;
private Notification mNotiGroupGSortA;
private Notification mNotiGroupGSortB;
@@ -85,6 +89,7 @@
private final int uid = 0;
private final String pkg2 = "pkg2";
private final int uid2 = 1111111;
+ private AudioAttributes mAudioAttributes;
private Context getContext() {
return InstrumentationRegistry.getTargetContext();
@@ -96,7 +101,7 @@
UserHandle user = UserHandle.ALL;
mHelper = new RankingHelper(getContext(), mPm, handler, mUsageStats,
- new String[] {ImportanceExtractor.class.getName()});
+ new String[]{ImportanceExtractor.class.getName()});
mNotiGroupGSortA = new Notification.Builder(getContext())
.setContentTitle("A")
@@ -143,6 +148,12 @@
"package", "package", 1, null, 0, 0, mNotiNoGroupSortA, user,
null, System.currentTimeMillis()), getDefaultChannel());
+ mAudioAttributes = new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
+ .build();
+
final ApplicationInfo legacy = new ApplicationInfo();
legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
final ApplicationInfo upgrade = new ApplicationInfo();
@@ -150,7 +161,8 @@
try {
when(mPm.getApplicationInfoAsUser(eq(pkg), anyInt(), anyInt())).thenReturn(legacy);
when(mPm.getApplicationInfoAsUser(eq(pkg2), anyInt(), anyInt())).thenReturn(upgrade);
- } catch (PackageManager.NameNotFoundException e) {}
+ } catch (PackageManager.NameNotFoundException e) {
+ }
}
private NotificationChannel getDefaultChannel() {
@@ -187,6 +199,7 @@
assertEquals(expected.canBypassDnd(), actual.canBypassDnd());
assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
assertEquals(expected.getGroup(), actual.getGroup());
+ assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
}
@Test
@@ -246,13 +259,13 @@
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
- channel2.setSound(new Uri.Builder().scheme("test").build());
+ channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
channel2.setLights(true);
channel2.setBypassDnd(true);
channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
channel2.enableVibration(true);
channel2.setGroup(ncg.getId());
- channel2.setVibrationPattern(new long[] {100, 67, 145, 156});
+ channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
mHelper.createNotificationChannelGroup(pkg, uid, ncg, true);
mHelper.createNotificationChannel(pkg, uid, channel1, true);
@@ -304,13 +317,13 @@
pkg, uid, NotificationChannel.DEFAULT_CHANNEL_ID, false);
assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, updated.getImportance());
assertFalse(updated.canBypassDnd());
- assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,updated.getLockscreenVisibility());
+ assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, updated.getLockscreenVisibility());
assertEquals(0, updated.getUserLockedFields());
}
@Test
public void testChannelXml_defaultChannelUpdatedApp_userSettings() throws Exception {
- NotificationChannel channel1 =
+ NotificationChannel channel1 =
new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_MIN);
mHelper.createNotificationChannel(pkg, uid, channel1, true);
@@ -335,15 +348,16 @@
@Test
public void testChannelXml_upgradeCreateDefaultChannel() throws Exception {
final String preupgradeXml = "<ranking version=\"1\">\n"
- + "<package name=\"" + pkg + "\" importance=\"" + NotificationManager.IMPORTANCE_HIGH
- + "\" priority=\"" + Notification.PRIORITY_MAX + "\" visibility=\""
- + Notification.VISIBILITY_SECRET + "\"" +" uid=\"" + uid + "\" />\n"
- + "<package name=\"" + pkg2 + "\" uid=\"" + uid2 + "\" visibility=\""
- + Notification.VISIBILITY_PRIVATE + "\" />\n"
- + "</ranking>";
+ + "<package name=\"" + pkg + "\" importance=\""
+ + NotificationManager.IMPORTANCE_HIGH
+ + "\" priority=\"" + Notification.PRIORITY_MAX + "\" visibility=\""
+ + Notification.VISIBILITY_SECRET + "\"" + " uid=\"" + uid + "\" />\n"
+ + "<package name=\"" + pkg2 + "\" uid=\"" + uid2 + "\" visibility=\""
+ + Notification.VISIBILITY_PRIVATE + "\" />\n"
+ + "</ranking>";
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new BufferedInputStream(new ByteArrayInputStream(preupgradeXml.getBytes())),
- null);
+ null);
parser.nextTag();
mHelper.readXml(parser, false);
@@ -353,8 +367,8 @@
assertTrue(updated1.canBypassDnd());
assertEquals(Notification.VISIBILITY_SECRET, updated1.getLockscreenVisibility());
assertEquals(NotificationChannel.USER_LOCKED_IMPORTANCE
- | NotificationChannel.USER_LOCKED_PRIORITY
- | NotificationChannel.USER_LOCKED_VISIBILITY, updated1.getUserLockedFields());
+ | NotificationChannel.USER_LOCKED_PRIORITY
+ | NotificationChannel.USER_LOCKED_VISIBILITY, updated1.getUserLockedFields());
final NotificationChannel updated2 = mHelper.getNotificationChannel(
pkg2, uid2, NotificationChannel.DEFAULT_CHANNEL_ID, false);
@@ -382,14 +396,14 @@
public void testUpdate_userLockedImportance() throws Exception {
// all fields locked by user
final NotificationChannel channel =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
mHelper.createNotificationChannel(pkg, uid, channel, false);
// same id, try to update
final NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
@@ -401,7 +415,7 @@
public void testUpdate_userLockedVisibility() throws Exception {
// all fields locked by user
final NotificationChannel channel =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
@@ -409,7 +423,7 @@
// same id, try to update
final NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
@@ -422,7 +436,7 @@
public void testUpdate_userLockedVibration() throws Exception {
// all fields locked by user
final NotificationChannel channel =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
channel.setLights(false);
channel.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
@@ -430,9 +444,9 @@
// same id, try to update
final NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.enableVibration(true);
- channel2.setVibrationPattern(new long[] {100});
+ channel2.setVibrationPattern(new long[]{100});
mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
@@ -444,7 +458,7 @@
public void testUpdate_userLockedLights() throws Exception {
// all fields locked by user
final NotificationChannel channel =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
channel.setLights(false);
channel.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
@@ -452,7 +466,7 @@
// same id, try to update
final NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.setLights(true);
mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
@@ -465,7 +479,7 @@
public void testUpdate_userLockedPriority() throws Exception {
// all fields locked by user
final NotificationChannel channel =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
channel.setBypassDnd(true);
channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
@@ -473,7 +487,7 @@
// same id, try to update all fields
final NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
channel2.setBypassDnd(false);
mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
@@ -486,16 +500,16 @@
public void testUpdate_userLockedRingtone() throws Exception {
// all fields locked by user
final NotificationChannel channel =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
- channel.setSound(new Uri.Builder().scheme("test").build());
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
+ channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
channel.lockFields(NotificationChannel.USER_LOCKED_SOUND);
mHelper.createNotificationChannel(pkg, uid, channel, false);
// same id, try to update all fields
final NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
- channel2.setSound(new Uri.Builder().scheme("test2").build());
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
+ channel2.setSound(new Uri.Builder().scheme("test2").build(), mAudioAttributes);
mHelper.updateNotificationChannelFromAssistant(pkg, uid, channel2);
@@ -527,7 +541,7 @@
// no fields locked by user
final NotificationChannel channel =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
- channel.setSound(new Uri.Builder().scheme("test").build());
+ channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
channel.setLights(true);
channel.setBypassDnd(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
@@ -537,7 +551,7 @@
// same id, try to update all fields
final NotificationChannel channel2 =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
- channel2.setSound(new Uri.Builder().scheme("test2").build());
+ channel2.setSound(new Uri.Builder().scheme("test2").build(), mAudioAttributes);
channel2.setLights(false);
channel2.setBypassDnd(false);
channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
@@ -559,7 +573,7 @@
public void testCreateChannel_CannotChangeHiddenFields() throws Exception {
final NotificationChannel channel =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
- channel.setSound(new Uri.Builder().scheme("test").build());
+ channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
channel.setLights(true);
channel.setBypassDnd(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
@@ -586,7 +600,7 @@
public void testCreateChannel_CannotChangeHiddenFieldsAssistant() throws Exception {
final NotificationChannel channel =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
- channel.setSound(new Uri.Builder().scheme("test").build());
+ channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
channel.setLights(true);
channel.setBypassDnd(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
@@ -613,12 +627,12 @@
public void testGetDeletedChannel() throws Exception {
NotificationChannel channel =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
- channel.setSound(new Uri.Builder().scheme("test").build());
+ channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
channel.setLights(true);
channel.setBypassDnd(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
channel.enableVibration(true);
- channel.setVibrationPattern(new long[] {100, 67, 145, 156});
+ channel.setVibrationPattern(new long[]{100, 67, 145, 156});
mHelper.createNotificationChannel(pkg, uid, channel, true);
mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
@@ -639,12 +653,12 @@
Map<String, NotificationChannel> channelMap = new HashMap<>();
NotificationChannel channel =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
- channel.setSound(new Uri.Builder().scheme("test").build());
+ channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
channel.setLights(true);
channel.setBypassDnd(true);
channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
channel.enableVibration(true);
- channel.setVibrationPattern(new long[] {100, 67, 145, 156});
+ channel.setVibrationPattern(new long[]{100, 67, 145, 156});
channelMap.put(channel.getId(), channel);
NotificationChannel channel2 =
new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
@@ -665,7 +679,7 @@
}
// Returns deleted channels too
- channels = mHelper.getNotificationChannels(pkg, uid, true).getList();
+ channels = mHelper.getNotificationChannels(pkg, uid, true).getList();
assertEquals(3, channels.size()); // Includes default channel
for (NotificationChannel nc : channels) {
if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
@@ -682,7 +696,7 @@
mHelper.deleteNotificationChannel(pkg, uid, channel.getId());
- channel.setSound(new Uri.Builder().scheme("test").build());
+ channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
try {
mHelper.updateNotificationChannel(pkg, uid, channel);
fail("Updated deleted channel");
@@ -700,7 +714,7 @@
@Test
public void testCreateDeletedChannel() throws Exception {
- long[] vibration = new long[] {100, 67, 145, 156};
+ long[] vibration = new long[]{100, 67, 145, 156};
NotificationChannel channel =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
channel.setVibrationPattern(vibration);
@@ -710,7 +724,7 @@
NotificationChannel newChannel = new NotificationChannel(
channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
- newChannel.setVibrationPattern(new long[] {100});
+ newChannel.setVibrationPattern(new long[]{100});
mHelper.createNotificationChannel(pkg, uid, newChannel, true);
@@ -721,7 +735,7 @@
@Test
public void testCreateChannel_alreadyExists() throws Exception {
- long[] vibration = new long[] {100, 67, 145, 156};
+ long[] vibration = new long[]{100, 67, 145, 156};
NotificationChannel channel =
new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_LOW);
channel.setVibrationPattern(vibration);
@@ -730,7 +744,7 @@
NotificationChannel newChannel = new NotificationChannel(
channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
- newChannel.setVibrationPattern(new long[] {100});
+ newChannel.setVibrationPattern(new long[]{100});
mHelper.createNotificationChannel(pkg, uid, newChannel, true);
@@ -816,7 +830,8 @@
try {
mHelper.createNotificationChannel(pkg, uid, channel1, true);
fail("Created a channel with a bad group");
- } catch (IllegalArgumentException e) {}
+ } catch (IllegalArgumentException e) {
+ }
}
@Test
@@ -862,11 +877,11 @@
List<NotificationChannelGroup> actual =
mHelper.getNotificationChannelGroups(pkg, uid, true).getList();
assertEquals(3, actual.size());
- for (NotificationChannelGroup group: actual) {
+ for (NotificationChannelGroup group : actual) {
if (group.getId() == null) {
assertEquals(2, group.getChannels().size()); // misc channel too
assertTrue(channel3.getId().equals(group.getChannels().get(0).getId())
- || channel3.getId().equals(group.getChannels().get(1).getId()));
+ || channel3.getId().equals(group.getChannels().get(1).getId()));
} else if (group.getId().equals(ncg.getId())) {
assertEquals(2, group.getChannels().size());
if (group.getChannels().get(0).getId().equals(channel1.getId())) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityGestureDetectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityGestureDetectorTest.java
new file mode 100644
index 0000000..d0c2b52
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityGestureDetectorTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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.server.accessibility;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+
+import android.accessibilityservice.AccessibilityService;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.os.Looper;
+import android.util.DisplayMetrics;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import java.util.ArrayList;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+
+/**
+ * Tests for AccessibilityGestureDetector
+ */
+public class AccessibilityGestureDetectorTest {
+
+ // Constants for testRecognizeGesturePath()
+ private static final PointF PATH_START = new PointF(300f, 300f);
+ private static final int PATH_STEP_PIXELS = 200;
+ private static final long PATH_STEP_MILLISEC = 100;
+
+ /**
+ * AccessibilitGestureDetector that can mock double-tap detector.
+ */
+ private class AccessibilityGestureDetectorTestable extends AccessibilityGestureDetector {
+ public AccessibilityGestureDetectorTestable(Context context, Listener listener) {
+ super(context, listener);
+ }
+
+ protected void setDoubleTapDetector(GestureDetector gestureDetector) {
+ mGestureDetector = gestureDetector;
+ mGestureDetector.setOnDoubleTapListener(this);
+ }
+ }
+
+
+ // Data used by all tests
+ private AccessibilityGestureDetectorTestable mDetector;
+ private AccessibilityGestureDetector.Listener mResultListener;
+
+
+ @BeforeClass
+ public static void oneTimeInitialization() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ }
+
+ @Before
+ public void setUp() {
+ // Construct a mock Context.
+ DisplayMetrics displayMetricsMock = mock(DisplayMetrics.class);
+ displayMetricsMock.xdpi = 500;
+ displayMetricsMock.ydpi = 500;
+ Resources mockResources = mock(Resources.class);
+ when(mockResources.getDisplayMetrics()).thenReturn(displayMetricsMock);
+ Context contextMock = mock(Context.class);
+ when(contextMock.getMainLooper()).thenReturn(Looper.myLooper());
+ when(contextMock.getResources()).thenReturn(mockResources);
+
+ // Construct a testable AccessibilityGestureDetector.
+ mResultListener = mock(AccessibilityGestureDetector.Listener.class);
+ mDetector = new AccessibilityGestureDetectorTestable(contextMock, mResultListener);
+ GestureDetector doubleTapDetectorMock = mock(GestureDetector.class);
+ mDetector.setDoubleTapDetector(doubleTapDetectorMock);
+ }
+
+
+ @Test
+ public void testRecognizeGesturePath() {
+ final int d = 1000; // Length of each segment in the test gesture, in pixels.
+
+ testPath(p(-d, +0), AccessibilityService.GESTURE_SWIPE_LEFT);
+ testPath(p(+d, +0), AccessibilityService.GESTURE_SWIPE_RIGHT);
+ testPath(p(+0, -d), AccessibilityService.GESTURE_SWIPE_UP);
+ testPath(p(+0, +d), AccessibilityService.GESTURE_SWIPE_DOWN);
+
+ testPath(p(-d, +0), p((-d - d), +0), AccessibilityService.GESTURE_SWIPE_LEFT);
+ testPath(p(-d, +0), p(+0, +0), AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT);
+ testPath(p(-d, +0), p(-d, -d), AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP);
+ testPath(p(-d, +0), p(-d, +d), AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN);
+
+ testPath(p(+d, +0), p(+0, +0), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT);
+ testPath(p(+d, +0), p((+d + d), +0), AccessibilityService.GESTURE_SWIPE_RIGHT);
+ testPath(p(+d, +0), p(+d, -d), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP);
+ testPath(p(+d, +0), p(+d, +d), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN);
+
+ testPath(p(+0, -d), p(-d, -d), AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT);
+ testPath(p(+0, -d), p(+d, -d), AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT);
+ testPath(p(+0, -d), p(+0, (-d - d)), AccessibilityService.GESTURE_SWIPE_UP);
+ testPath(p(+0, -d), p(+0, +0), AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN);
+
+ testPath(p(+0, +d), p(-d, +d), AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT);
+ testPath(p(+0, +d), p(+d, +d), AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT);
+ testPath(p(+0, +d), p(+0, +0), AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP);
+ testPath(p(+0, +d), p(+0, (+d + d)), AccessibilityService.GESTURE_SWIPE_DOWN);
+ }
+
+ /** Convenient short alias to make a Point. */
+ private static Point p(int x, int y) {
+ return new Point(x, y);
+ }
+
+ /** Test recognizing path from PATH_START to PATH_START+delta. */
+ private void testPath(Point delta, int gestureId) {
+ ArrayList<PointF> path = new ArrayList<>();
+ path.add(PATH_START);
+
+ PointF segmentEnd = new PointF(PATH_START.x + delta.x, PATH_START.y + delta.y);
+ fillPath(PATH_START, segmentEnd, path);
+
+ testPath(path, gestureId);
+ }
+
+ /** Test recognizing path from PATH_START to PATH_START+delta1 to PATH_START+delta2. */
+ private void testPath(Point delta1, Point delta2, int gestureId) {
+ ArrayList<PointF> path = new ArrayList<>();
+ path.add(PATH_START);
+
+ PointF startPlusDelta1 = new PointF(PATH_START.x + delta1.x, PATH_START.y + delta1.y);
+ fillPath(PATH_START, startPlusDelta1, path);
+
+ PointF startPlusDelta2 = new PointF(PATH_START.x + delta2.x, PATH_START.y + delta2.y);
+ fillPath(startPlusDelta1, startPlusDelta2, path);
+
+ testPath(path, gestureId);
+ }
+
+ /** Fill in movement points from start to end, appending points to path. */
+ private void fillPath(PointF start, PointF end, ArrayList<PointF> path) {
+ // Calculate number of path steps needed.
+ float deltaX = end.x - start.x;
+ float deltaY = end.y - start.y;
+ float distance = (float) Math.hypot(deltaX, deltaY);
+ float numSteps = distance / (float) PATH_STEP_PIXELS;
+ float stepX = (float) deltaX / numSteps;
+ float stepY = (float) deltaY / numSteps;
+
+ // For each path step from start (non-inclusive) to end ... add a motion point.
+ for (int step = 1; step < numSteps; ++step) {
+ path.add(new PointF(
+ (start.x + (stepX * (float) step)),
+ (start.y + (stepY * (float) step))));
+ }
+ }
+
+ /** Test recognizing a path made of motion event points. */
+ private void testPath(ArrayList<PointF> path, int gestureId) {
+ // Clear last recognition result.
+ reset(mResultListener);
+
+ int policyFlags = 0;
+ long eventDownTimeMs = 0;
+ long eventTimeMs = eventDownTimeMs;
+
+ // For each path point...
+ for (int pointIndex = 0; pointIndex < path.size(); ++pointIndex) {
+
+ // Create motion event.
+ PointF point = path.get(pointIndex);
+ int action = MotionEvent.ACTION_MOVE;
+ if (pointIndex == 0) {
+ action = MotionEvent.ACTION_DOWN;
+ } else if (pointIndex == path.size() - 1) {
+ action = MotionEvent.ACTION_UP;
+ }
+ MotionEvent event = MotionEvent.obtain(eventDownTimeMs, eventTimeMs, action,
+ point.x, point.y, 0);
+
+ // Send event.
+ mDetector.onMotionEvent(event, policyFlags);
+ eventTimeMs += PATH_STEP_MILLISEC;
+ }
+
+ // Check that correct gesture was recognized.
+ verify(mResultListener).onGestureCompleted(gestureId);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index 2d07e65..90a2ec0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -73,8 +73,7 @@
mInvalidIsa = new TestData("INVALID", "INVALID_ISA", mUser0);
mDoesNotExist = new TestData("DOES.NOT.EXIST", isa, mUser1);
-
- mDexManager = new DexManager();
+ mDexManager = new DexManager(null, null, null, null);
// Foo and Bar are available to user0.
// Only Bar is available to user1;
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java
index 5a42841..19e0bcf 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java
@@ -256,6 +256,32 @@
assertNull(mPackageDexUsage.getPackageUseInfo(mFooBaseUser0.mPackageName));
}
+ @Test
+ public void testRemoveUserPackage() {
+ // Record Bar secondaries for two different users.
+ assertTrue(record(mBarSecondary1User0));
+ assertTrue(record(mBarSecondary2User1));
+
+ // Remove user 0 files.
+ assertTrue(mPackageDexUsage.removeUserPackage(mBarSecondary1User0.mPackageName,
+ mBarSecondary1User0.mOwnerUserId));
+ // Assert that only user 1 files are there.
+ assertPackageDexUsage(null, mBarSecondary2User1);
+ }
+
+ @Test
+ public void testRemoveDexFile() {
+ // Record Bar secondaries for two different users.
+ assertTrue(record(mBarSecondary1User0));
+ assertTrue(record(mBarSecondary2User1));
+
+ // Remove mBarSecondary1User0 file.
+ assertTrue(mPackageDexUsage.removeDexFile(mBarSecondary1User0.mPackageName,
+ mBarSecondary1User0.mDexFile, mBarSecondary1User0.mOwnerUserId));
+ // Assert that only user 1 files are there.
+ assertPackageDexUsage(null, mBarSecondary2User1);
+ }
+
private void assertPackageDexUsage(TestData primary, TestData... secondaries) {
String packageName = primary == null ? secondaries[0].mPackageName : primary.mPackageName;
boolean primaryUsedByOtherApps = primary == null ? false : primary.mUsedByOtherApps;