Merge "Disable RT animations for notification actions"
diff --git a/Android.mk b/Android.mk
index 7ca8358..10d11f3 100644
--- a/Android.mk
+++ b/Android.mk
@@ -656,6 +656,10 @@
# Check comment when you are updating the API
update-api: doc-comment-check-docs
+# Generate API diffs as part of docs builds
+docs: offline-sdk-referenceonly-diff
+docs: offline-system-sdk-referenceonly-diff
+
# ==== static html in the sdk ==================================
include $(CLEAR_VARS)
diff --git a/api/current.txt b/api/current.txt
index a30d560..f56f1a1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9974,6 +9974,7 @@
}
public class QuickViewConstants {
+ field public static final java.lang.String FEATURE_DELETE = "android:delete";
field public static final java.lang.String FEATURE_DOWNLOAD = "android:download";
field public static final java.lang.String FEATURE_EDIT = "android:edit";
field public static final java.lang.String FEATURE_PRINT = "android:print";
diff --git a/core/java/android/app/VrManager.java b/core/java/android/app/VrManager.java
index 392387a..61b90e1 100644
--- a/core/java/android/app/VrManager.java
+++ b/core/java/android/app/VrManager.java
@@ -4,6 +4,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.os.Handler;
@@ -214,4 +215,22 @@
e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Start VR Input method for the packageName in {@link ComponentName}.
+ * This method notifies InputMethodManagerService to use VR IME instead of
+ * regular phone IME.
+ * @param componentName ComponentName of a VR InputMethod that should be set as selected
+ * input by InputMethodManagerService.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+ public void setVrInputMethod(ComponentName componentName) {
+ try {
+ mService.setVrInputMethod(componentName);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 28bd928..6fb1afd 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4915,8 +4915,9 @@
* <li>Enumeration of features here is not meant to restrict capabilities of the quick viewer.
* Quick viewer can implement features not listed below.
* <li>Features included at this time are: {@link QuickViewConstants#FEATURE_VIEW},
- * {@link QuickViewConstants#FEATURE_EDIT}, {@link QuickViewConstants#FEATURE_DOWNLOAD},
- * {@link QuickViewConstants#FEATURE_SEND}, {@link QuickViewConstants#FEATURE_PRINT}.
+ * {@link QuickViewConstants#FEATURE_EDIT}, {@link QuickViewConstants#FEATURE_DELETE},
+ * {@link QuickViewConstants#FEATURE_DOWNLOAD}, {@link QuickViewConstants#FEATURE_SEND},
+ * {@link QuickViewConstants#FEATURE_PRINT}.
* <p>
* Requirements:
* <li>Quick viewer shouldn't show a feature if the feature is absent in
diff --git a/core/java/android/content/QuickViewConstants.java b/core/java/android/content/QuickViewConstants.java
index 7455d0c..a25513d 100644
--- a/core/java/android/content/QuickViewConstants.java
+++ b/core/java/android/content/QuickViewConstants.java
@@ -33,7 +33,7 @@
public static final String FEATURE_VIEW = "android:view";
/**
- * Feature to view a document using system standard editing mechanism, like
+ * Feature to edit a document using system standard editing mechanism, like
* {@link Intent#ACTION_EDIT}.
*
* @see Intent#EXTRA_QUICK_VIEW_FEATURES
@@ -42,6 +42,15 @@
public static final String FEATURE_EDIT = "android:edit";
/**
+ * Feature to delete an individual document. Quick viewer implementations must use
+ * Storage Access Framework to both verify delete permission and to delete content.
+ *
+ * @see DocumentsContract#Document#FLAG_SUPPORTS_DELETE
+ * @see DocumentsContract#deleteDocument(ContentResolver resolver, Uri documentUri)
+ */
+ public static final String FEATURE_DELETE = "android:delete";
+
+ /**
* Feature to view a document using system standard sending mechanism, like
* {@link Intent#ACTION_SEND}.
*
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 9e54e23..d09ba0b 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -265,6 +265,14 @@
/**
* Include pinned shortcuts in the result.
+ *
+ * <p>If you are the selected assistant app, and wishes to fetch all shortcuts that the
+ * user owns on the launcher (or by other launchers, in case the user has multiple), use
+ * {@link #FLAG_MATCH_PINNED_BY_ANY_LAUNCHER} instead.
+ *
+ * <p>If you're a regular launcher app, there's no way to get shortcuts pinned by other
+ * launchers, and {@link #FLAG_MATCH_PINNED_BY_ANY_LAUNCHER} will be ignored. So use this
+ * flag to get own pinned shortcuts.
*/
public static final int FLAG_MATCH_PINNED = 1 << 1;
@@ -285,8 +293,15 @@
* Include all pinned shortcuts by any launchers, not just by the caller,
* in the result.
*
- * The caller must be the selected assistant app to use this flag, or have the system
+ * <p>The caller must be the selected assistant app to use this flag, or have the system
* {@code ACCESS_SHORTCUTS} permission.
+ *
+ * <p>If you are the selected assistant app, and wishes to fetch all shortcuts that the
+ * user owns on the launcher (or by other launchers, in case the user has multiple), use
+ * {@link #FLAG_MATCH_PINNED_BY_ANY_LAUNCHER} instead.
+ *
+ * <p>If you're a regular launcher app (or any app that's not the selected assistant app)
+ * then this flag will be ignored.
*/
public static final int FLAG_MATCH_PINNED_BY_ANY_LAUNCHER = 1 << 10;
diff --git a/core/java/android/content/res/XmlResourceParser.java b/core/java/android/content/res/XmlResourceParser.java
index 5af49d4..6be9b9e 100644
--- a/core/java/android/content/res/XmlResourceParser.java
+++ b/core/java/android/content/res/XmlResourceParser.java
@@ -16,20 +16,19 @@
package android.content.res;
-import org.xmlpull.v1.XmlPullParser;
-
import android.util.AttributeSet;
+import org.xmlpull.v1.XmlPullParser;
+
/**
* The XML parsing interface returned for an XML resource. This is a standard
- * XmlPullParser interface, as well as an extended AttributeSet interface and
- * an additional close() method on this interface for the client to indicate
- * when it is done reading the resource.
+ * {@link XmlPullParser} interface but also extends {@link AttributeSet} and
+ * adds an additional {@link #close()} method for the client to indicate when
+ * it is done reading the resource.
*/
public interface XmlResourceParser extends XmlPullParser, AttributeSet, AutoCloseable {
/**
- * Close this interface to the resource. Calls on the interface are no
- * longer value after this call.
+ * Close this parser. Calls on the interface are no longer valid after this call.
*/
public void close();
}
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index a7a3df7..cb11d0f 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -2729,13 +2729,13 @@
//
/**
- * <p>Scene change is not detected within AF regions.</p>
+ * <p>Scene change is not detected within the AF region(s).</p>
* @see CaptureResult#CONTROL_AF_SCENE_CHANGE
*/
public static final int CONTROL_AF_SCENE_CHANGE_NOT_DETECTED = 0;
/**
- * <p>Scene change is detected within AF regions.</p>
+ * <p>Scene change is detected within the AF region(s).</p>
* @see CaptureResult#CONTROL_AF_SCENE_CHANGE
*/
public static final int CONTROL_AF_SCENE_CHANGE_DETECTED = 1;
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index b7d7f7d..6d7b06f 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2185,10 +2185,16 @@
new Key<Boolean>("android.control.enableZsl", boolean.class);
/**
- * <p>Whether scene change is detected within AF regions.</p>
- * <p>When AF detects a scene change within current AF regions, it will be set to DETECTED. Otherwise,
- * it will be set to NOT_DETECTED. This value will remain NOT_DETECTED if afMode is AF_MODE_OFF or
- * AF_MODE_EDOF.</p>
+ * <p>Whether a significant scene change is detected within the currently-set AF
+ * region(s).</p>
+ * <p>When the camera focus routine detects a change in the scene it is looking at,
+ * such as a large shift in camera viewpoint, significant motion in the scene, or a
+ * significant illumination change, this value will be set to DETECTED for a single capture
+ * result. Otherwise the value will be NOT_DETECTED. The threshold for detection is similar
+ * to what would trigger a new passive focus scan to begin in CONTINUOUS autofocus modes.</p>
+ * <p>afSceneChange may be DETECTED only if afMode is AF_MODE_CONTINUOUS_VIDEO or
+ * AF_MODE_CONTINUOUS_PICTURE. In other AF modes, afSceneChange must be NOT_DETECTED.</p>
+ * <p>This key will be available if the camera device advertises this key via {@link android.hardware.camera2.CameraCharacteristics#getAvailableCaptureResultKeys }.</p>
* <p><b>Possible values:</b>
* <ul>
* <li>{@link #CONTROL_AF_SCENE_CHANGE_NOT_DETECTED NOT_DETECTED}</li>
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index a8acb97..766ad84 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -224,6 +224,12 @@
public static final int FEATURES_WIFI = 0x8;
/**
+ * Indicates the call underwent Assisted Dialing.
+ * @hide
+ */
+ public static final Integer FEATURES_ASSISTED_DIALING_USED = 0x10;
+
+ /**
* The phone number as the user entered it.
* <P>Type: TEXT</P>
*/
diff --git a/core/java/android/service/vr/IVrManager.aidl b/core/java/android/service/vr/IVrManager.aidl
index 7285fb4..f7acfc5 100644
--- a/core/java/android/service/vr/IVrManager.aidl
+++ b/core/java/android/service/vr/IVrManager.aidl
@@ -17,6 +17,7 @@
package android.service.vr;
import android.app.Vr2dDisplayProperties;
+import android.content.ComponentName;
import android.service.vr.IVrStateCallbacks;
import android.service.vr.IPersistentVrStateCallbacks;
@@ -109,5 +110,13 @@
* @param standy True if the device is entering standby, false if it's exiting standby.
*/
void setStandbyEnabled(boolean standby);
+
+ /**
+ * Start VR Input method for the given packageName in {@param componentName}.
+ * This method notifies InputMethodManagerService to use VR IME instead of
+ * regular phone IME.
+ */
+ void setVrInputMethod(in ComponentName componentName);
+
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 92d1de8..4d96733 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -697,6 +697,19 @@
}
}
+ /**
+ * Returns a list of VR InputMethod currently installed.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS)
+ public List<InputMethodInfo> getVrInputMethodList() {
+ try {
+ return mService.getVrInputMethodList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
public List<InputMethodInfo> getEnabledInputMethodList() {
try {
return mService.getEnabledInputMethodList();
diff --git a/core/java/android/view/inputmethod/InputMethodManagerInternal.java b/core/java/android/view/inputmethod/InputMethodManagerInternal.java
index 77df4e3..e13813e 100644
--- a/core/java/android/view/inputmethod/InputMethodManagerInternal.java
+++ b/core/java/android/view/inputmethod/InputMethodManagerInternal.java
@@ -16,6 +16,8 @@
package android.view.inputmethod;
+import android.content.ComponentName;
+
/**
* Input method manager local system service interface.
*
@@ -37,4 +39,9 @@
* Hides the current input method, if visible.
*/
void hideCurrentInputMethod();
+
+ /**
+ * Switches to VR InputMethod defined in the packageName of {@param componentName}.
+ */
+ void startVrInputMethodNoCheck(ComponentName componentName);
}
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index 1279040..7156300 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -570,11 +570,12 @@
mFormatChangeObserver = new FormatChangeObserver(getHandler());
}
final ContentResolver resolver = getContext().getContentResolver();
+ Uri uri = Settings.System.getUriFor(Settings.System.TIME_12_24);
if (mShowCurrentUserTime) {
- resolver.registerContentObserver(Settings.System.CONTENT_URI, true,
+ resolver.registerContentObserver(uri, true,
mFormatChangeObserver, UserHandle.USER_ALL);
} else {
- resolver.registerContentObserver(Settings.System.CONTENT_URI, true,
+ resolver.registerContentObserver(uri, true,
mFormatChangeObserver);
}
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodUtils.java b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
index 3e231d0..57efae6 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodUtils.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodUtils.java
@@ -836,7 +836,6 @@
private final Resources mRes;
private final ContentResolver mResolver;
private final HashMap<String, InputMethodInfo> mMethodMap;
- private final ArrayList<InputMethodInfo> mMethodList;
/**
* On-memory data store to emulate when {@link #mCopyOnWrite} is {@code true}.
@@ -906,7 +905,6 @@
mRes = res;
mResolver = resolver;
mMethodMap = methodMap;
- mMethodList = methodList;
switchCurrentUser(userId, copyOnWrite);
}
@@ -1087,7 +1085,7 @@
final ArrayList<InputMethodInfo> res = new ArrayList<>();
for (Pair<String, ArrayList<String>> ims: imsList) {
InputMethodInfo info = mMethodMap.get(ims.first);
- if (info != null) {
+ if (info != null && !info.isVrOnly()) {
res.add(info);
}
}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index b979807..ca8624d 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -36,6 +36,7 @@
interface IInputMethodManager {
// TODO: Use ParceledListSlice instead
List<InputMethodInfo> getInputMethodList();
+ List<InputMethodInfo> getVrInputMethodList();
// TODO: Use ParceledListSlice instead
List<InputMethodInfo> getEnabledInputMethodList();
List<InputMethodSubtype> getEnabledInputMethodSubtypeList(in String imiId,
diff --git a/core/java/com/android/internal/view/RotationPolicy.java b/core/java/com/android/internal/view/RotationPolicy.java
index b479cb1..d7b9132 100644
--- a/core/java/com/android/internal/view/RotationPolicy.java
+++ b/core/java/com/android/internal/view/RotationPolicy.java
@@ -108,11 +108,19 @@
* Enables or disables rotation lock from the system UI toggle.
*/
public static void setRotationLock(Context context, final boolean enabled) {
+ final int rotation = areAllRotationsAllowed(context) ? CURRENT_ROTATION : NATURAL_ROTATION;
+ setRotationLockAtAngle(context, enabled, rotation);
+ }
+
+ /**
+ * Enables or disables rotation lock at a specific rotation from system UI.
+ */
+ public static void setRotationLockAtAngle(Context context, final boolean enabled,
+ final int rotation) {
Settings.System.putIntForUser(context.getContentResolver(),
Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0,
UserHandle.USER_CURRENT);
- final int rotation = areAllRotationsAllowed(context) ? CURRENT_ROTATION : NATURAL_ROTATION;
setRotationLock(enabled, rotation);
}
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 968f596..d15ab33 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -184,6 +184,17 @@
public static final String MODE_CHANGED_ACTION = "android.location.MODE_CHANGED";
/**
+ * Broadcast intent action when {@link android.provider.Settings.Secure#LOCATION_MODE} is
+ * about to be changed through Settings app or Quick Settings.
+ * For use with the {@link android.provider.Settings.Secure#LOCATION_MODE} API.
+ * If you're interacting with {@link #isProviderEnabled(String)}, use
+ * {@link #PROVIDERS_CHANGED_ACTION} instead.
+ *
+ * @hide
+ */
+ public static final String MODE_CHANGING_ACTION = "com.android.settings.location.MODE_CHANGING";
+
+ /**
* Broadcast intent action indicating that the GPS has either started or
* stopped receiving GPS fixes. An intent extra provides this state as a
* boolean, where {@code true} means that the GPS is actively receiving fixes.
diff --git a/packages/EasterEgg/Android.mk b/packages/EasterEgg/Android.mk
index d4c1e70..a825581 100644
--- a/packages/EasterEgg/Android.mk
+++ b/packages/EasterEgg/Android.mk
@@ -2,17 +2,23 @@
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
+
LOCAL_STATIC_JAVA_LIBRARIES := \
+ jsr305
+
+LOCAL_STATIC_ANDROID_LIBRARIES := \
android-support-v4 \
android-support-v13 \
android-support-dynamic-animation \
android-support-v7-recyclerview \
android-support-v7-preference \
android-support-v7-appcompat \
- android-support-v14-preference \
- jsr305
+ android-support-v14-preference
+
+LOCAL_USE_AAPT2 := true
LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_PACKAGE_NAME := EasterEgg
LOCAL_CERTIFICATE := platform
diff --git a/packages/SettingsLib/common.mk b/packages/SettingsLib/common.mk
index 49c5467..9d1c4ca 100644
--- a/packages/SettingsLib/common.mk
+++ b/packages/SettingsLib/common.mk
@@ -21,6 +21,10 @@
LOCAL_STATIC_ANDROID_LIBRARIES += \
android-support-v4 \
apptoolkit-lifecycle-runtime \
+ android-support-v7-recyclerview \
+ android-support-v7-preference \
+ android-support-v7-appcompat \
+ android-support-v14-preference \
SettingsLib
else
LOCAL_RESOURCE_DIR += $(call my-dir)/res
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index eb33842..3c46d99 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -14,8 +14,10 @@
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
+import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.os.BatteryManager;
+import android.os.UserHandle;
import android.os.UserManager;
import android.print.PrintManager;
import android.provider.Settings;
@@ -26,6 +28,10 @@
import java.text.NumberFormat;
public class Utils {
+
+ private static final String CURRENT_MODE_KEY = "CURRENT_MODE";
+ private static final String NEW_MODE_KEY = "NEW_MODE";
+
private static Signature[] sSystemSignature;
private static String sPermissionControllerPackageName;
private static String sServicesSystemSharedLibPackageName;
@@ -39,6 +45,16 @@
com.android.internal.R.drawable.ic_wifi_signal_4
};
+ public static boolean updateLocationMode(Context context, int oldMode, int newMode, int userId) {
+ Intent intent = new Intent(LocationManager.MODE_CHANGING_ACTION);
+ intent.putExtra(CURRENT_MODE_KEY, oldMode);
+ intent.putExtra(NEW_MODE_KEY, newMode);
+ context.sendBroadcastAsUser(
+ intent, UserHandle.of(userId), android.Manifest.permission.WRITE_SECURE_SETTINGS);
+ return Settings.Secure.putIntForUser(
+ context.getContentResolver(), Settings.Secure.LOCATION_MODE, newMode, userId);
+ }
+
/**
* Return string resource that best describes combination of tethering
* options available on this device.
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
index a966e82..3a03644 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
@@ -75,23 +75,27 @@
* Note: the returned list serves as a read-only list. If tiles needs to be added or removed
* from the actual tiles list, it should be done through {@link #addTile}, {@link #removeTile}.
*/
- public List<Tile> getTiles() {
- return Collections.unmodifiableList(mTiles);
+ public synchronized List<Tile> getTiles() {
+ final List<Tile> result = new ArrayList<>(mTiles.size());
+ for (Tile tile : mTiles) {
+ result.add(tile);
+ }
+ return result;
}
- public void addTile(Tile tile) {
+ public synchronized void addTile(Tile tile) {
mTiles.add(tile);
}
- public void addTile(int n, Tile tile) {
+ public synchronized void addTile(int n, Tile tile) {
mTiles.add(n, tile);
}
- public void removeTile(Tile tile) {
+ public synchronized void removeTile(Tile tile) {
mTiles.remove(tile);
}
- public void removeTile(int n) {
+ public synchronized void removeTile(int n) {
mTiles.remove(n);
}
@@ -103,7 +107,7 @@
return mTiles.get(n);
}
- public boolean containsComponent(ComponentName component) {
+ public synchronized boolean containsComponent(ComponentName component) {
for (Tile tile : mTiles) {
if (TextUtils.equals(tile.intent.getComponent().getClassName(),
component.getClassName())) {
@@ -129,7 +133,7 @@
/**
* Sort priority value and package name for tiles in this category.
*/
- public void sortTiles(String skipPackageName) {
+ public synchronized void sortTiles(String skipPackageName) {
// Sort mTiles based on [priority, package within priority]
Collections.sort(mTiles, (tile1, tile2) -> {
final String package1 = tile1.intent.getComponent().getPackageName();
diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
new file mode 100644
index 0000000..e3e27ce1
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXml.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.license;
+
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.GZIPInputStream;
+
+/**
+ * The utility class that generate a license html file from xml files.
+ * All the HTML snippets and logic are copied from build/make/tools/generate-notice-files.py.
+ *
+ * TODO: Remove duplicate codes once backward support ends.
+ */
+class LicenseHtmlGeneratorFromXml {
+ private static final String TAG = "LicenseHtmlGeneratorFromXml";
+
+ private static final String TAG_ROOT = "licenses";
+ private static final String TAG_FILE_NAME = "file-name";
+ private static final String TAG_FILE_CONTENT = "file-content";
+ private static final String ATTR_CONTENT_ID = "contentId";
+
+ private static final String HTML_HEAD_STRING =
+ "<html><head>\n"
+ + "<style type=\"text/css\">\n"
+ + "body { padding: 0; font-family: sans-serif; }\n"
+ + ".same-license { background-color: #eeeeee;\n"
+ + " border-top: 20px solid white;\n"
+ + " padding: 10px; }\n"
+ + ".label { font-weight: bold; }\n"
+ + ".file-list { margin-left: 1em; color: blue; }\n"
+ + "</style>\n"
+ + "</head>"
+ + "<body topmargin=\"0\" leftmargin=\"0\" rightmargin=\"0\" bottommargin=\"0\">\n"
+ + "<div class=\"toc\">\n"
+ + "<ul>";
+
+ private static final String HTML_MIDDLE_STRING =
+ "</ul>\n"
+ + "</div><!-- table of contents -->\n"
+ + "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">";
+
+ private static final String HTML_REAR_STRING =
+ "</table></body></html>";
+
+ private final List<File> mXmlFiles;
+
+ /*
+ * A map from a file name to a content id (MD5 sum of file content) for its license.
+ * For example, "/system/priv-app/TeleService/TeleService.apk" maps to
+ * "9645f39e9db895a4aa6e02cb57294595". Here "9645f39e9db895a4aa6e02cb57294595" is a MD5 sum
+ * of the content of packages/services/Telephony/MODULE_LICENSE_APACHE2.
+ */
+ private final Map<String, String> mFileNameToContentIdMap = new HashMap();
+
+ /*
+ * A map from a content id (MD5 sum of file content) to a license file content.
+ * For example, "9645f39e9db895a4aa6e02cb57294595" maps to the content string of
+ * packages/services/Telephony/MODULE_LICENSE_APACHE2. Here "9645f39e9db895a4aa6e02cb57294595"
+ * is a MD5 sum of the file content.
+ */
+ private final Map<String, String> mContentIdToFileContentMap = new HashMap();
+
+ static class ContentIdAndFileNames {
+ final String mContentId;
+ final List<String> mFileNameList = new ArrayList();
+
+ ContentIdAndFileNames(String contentId) {
+ mContentId = contentId;
+ }
+ }
+
+ private LicenseHtmlGeneratorFromXml(List<File> xmlFiles) {
+ mXmlFiles = xmlFiles;
+ }
+
+ public static boolean generateHtml(List<File> xmlFiles, File outputFile) {
+ LicenseHtmlGeneratorFromXml genertor = new LicenseHtmlGeneratorFromXml(xmlFiles);
+ return genertor.generateHtml(outputFile);
+ }
+
+ private boolean generateHtml(File outputFile) {
+ for (File xmlFile : mXmlFiles) {
+ parse(xmlFile);
+ }
+
+ if (mFileNameToContentIdMap.isEmpty() || mContentIdToFileContentMap.isEmpty()) {
+ return false;
+ }
+
+ PrintWriter writer = null;
+ try {
+ writer = new PrintWriter(outputFile);
+
+ generateHtml(mFileNameToContentIdMap, mContentIdToFileContentMap, writer);
+
+ writer.flush();
+ writer.close();
+ return true;
+ } catch (FileNotFoundException | SecurityException e) {
+ Log.e(TAG, "Failed to generate " + outputFile, e);
+
+ if (writer != null) {
+ writer.close();
+ }
+ return false;
+ }
+ }
+
+ private void parse(File xmlFile) {
+ if (xmlFile == null || !xmlFile.exists() || xmlFile.length() == 0) {
+ return;
+ }
+
+ InputStreamReader in = null;
+ try {
+ if (xmlFile.getName().endsWith(".gz")) {
+ in = new InputStreamReader(new GZIPInputStream(new FileInputStream(xmlFile)));
+ } else {
+ in = new FileReader(xmlFile);
+ }
+
+ parse(in, mFileNameToContentIdMap, mContentIdToFileContentMap);
+
+ in.close();
+ } catch (XmlPullParserException | IOException e) {
+ Log.e(TAG, "Failed to parse " + xmlFile, e);
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException ie) {
+ Log.w(TAG, "Failed to close " + xmlFile);
+ }
+ }
+ }
+ }
+
+ /*
+ * Parses an input stream and fills a map from a file name to a content id for its license
+ * and a map from a content id to a license file content.
+ *
+ * Following xml format is expected from the input stream.
+ *
+ * <licenses>
+ * <file-name contentId="content_id_of_license1">file1</file-name>
+ * <file-name contentId="content_id_of_license2">file2</file-name>
+ * ...
+ * <file-content contentId="content_id_of_license1">license1 file contents</file-content>
+ * <file-content contentId="content_id_of_license2">license2 file contents</file-content>
+ * ...
+ * </licenses>
+ */
+ @VisibleForTesting
+ static void parse(InputStreamReader in, Map<String, String> outFileNameToContentIdMap,
+ Map<String, String> outContentIdToFileContentMap)
+ throws XmlPullParserException, IOException {
+ Map<String, String> fileNameToContentIdMap = new HashMap<String, String>();
+ Map<String, String> contentIdToFileContentMap = new HashMap<String, String>();
+
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in);
+ parser.nextTag();
+
+ parser.require(XmlPullParser.START_TAG, "", TAG_ROOT);
+
+ int state = parser.getEventType();
+ while (state != XmlPullParser.END_DOCUMENT) {
+ if (state == XmlPullParser.START_TAG) {
+ if (TAG_FILE_NAME.equals(parser.getName())) {
+ String contentId = parser.getAttributeValue("", ATTR_CONTENT_ID);
+ if (!TextUtils.isEmpty(contentId)) {
+ String fileName = readText(parser).trim();
+ if (!TextUtils.isEmpty(fileName)) {
+ fileNameToContentIdMap.put(fileName, contentId);
+ }
+ }
+ } else if (TAG_FILE_CONTENT.equals(parser.getName())) {
+ String contentId = parser.getAttributeValue("", ATTR_CONTENT_ID);
+ if (!TextUtils.isEmpty(contentId)
+ && !outContentIdToFileContentMap.containsKey(contentId)
+ && !contentIdToFileContentMap.containsKey(contentId)) {
+ String fileContent = readText(parser);
+ if (!TextUtils.isEmpty(fileContent)) {
+ contentIdToFileContentMap.put(contentId, fileContent);
+ }
+ }
+ }
+ }
+
+ state = parser.next();
+ }
+ outFileNameToContentIdMap.putAll(fileNameToContentIdMap);
+ outContentIdToFileContentMap.putAll(contentIdToFileContentMap);
+ }
+
+ private static String readText(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ StringBuffer result = new StringBuffer();
+ int state = parser.next();
+ while (state == XmlPullParser.TEXT) {
+ result.append(parser.getText());
+ state = parser.next();
+ }
+ return result.toString();
+ }
+
+ @VisibleForTesting
+ static void generateHtml(Map<String, String> fileNameToContentIdMap,
+ Map<String, String> contentIdToFileContentMap, PrintWriter writer) {
+ List<String> fileNameList = new ArrayList();
+ fileNameList.addAll(fileNameToContentIdMap.keySet());
+ Collections.sort(fileNameList);
+
+ writer.println(HTML_HEAD_STRING);
+
+ int count = 0;
+ Map<String, Integer> contentIdToOrderMap = new HashMap();
+ List<ContentIdAndFileNames> contentIdAndFileNamesList = new ArrayList();
+
+ // Prints all the file list with a link to its license file content.
+ for (String fileName : fileNameList) {
+ String contentId = fileNameToContentIdMap.get(fileName);
+ // Assigns an id to a newly referred license file content.
+ if (!contentIdToOrderMap.containsKey(contentId)) {
+ contentIdToOrderMap.put(contentId, count);
+
+ // An index in contentIdAndFileNamesList is the order of each element.
+ contentIdAndFileNamesList.add(new ContentIdAndFileNames(contentId));
+ count++;
+ }
+
+ int id = contentIdToOrderMap.get(contentId);
+ contentIdAndFileNamesList.get(id).mFileNameList.add(fileName);
+ writer.format("<li><a href=\"#id%d\">%s</a></li>\n", id, fileName);
+ }
+
+ writer.println(HTML_MIDDLE_STRING);
+
+ count = 0;
+ // Prints all contents of the license files in order of id.
+ for (ContentIdAndFileNames contentIdAndFileNames : contentIdAndFileNamesList) {
+ writer.format("<tr id=\"id%d\"><td class=\"same-license\">\n", count);
+ writer.println("<div class=\"label\">Notices for file(s):</div>");
+ writer.println("<div class=\"file-list\">");
+ for (String fileName : contentIdAndFileNames.mFileNameList) {
+ writer.format("%s <br/>\n", fileName);
+ }
+ writer.println("</div><!-- file-list -->");
+ writer.println("<pre class=\"license-text\">");
+ writer.println(contentIdToFileContentMap.get(
+ contentIdAndFileNames.mContentId));
+ writer.println("</pre><!-- license-text -->");
+ writer.println("</td></tr><!-- same-license -->");
+
+ count++;
+ }
+
+ writer.println(HTML_REAR_STRING);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java
new file mode 100644
index 0000000..a9fb20c
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoader.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.license;
+
+import android.content.Context;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+
+import com.android.settingslib.utils.AsyncLoader;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * LicenseHtmlLoader is a loader which loads a license html file from default license xml files.
+ */
+public class LicenseHtmlLoader extends AsyncLoader<File> {
+ private static final String TAG = "LicenseHtmlLoader";
+
+ private static final String[] DEFAULT_LICENSE_XML_PATHS = {
+ "/system/etc/NOTICE.xml.gz",
+ "/vendor/etc/NOTICE.xml.gz",
+ "/odm/etc/NOTICE.xml.gz",
+ "/oem/etc/NOTICE.xml.gz"};
+ private static final String NOTICE_HTML_FILE_NAME = "NOTICE.html";
+
+ private Context mContext;
+
+ public LicenseHtmlLoader(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ @Override
+ public File loadInBackground() {
+ return generateHtmlFromDefaultXmlFiles();
+ }
+
+ @Override
+ protected void onDiscardResult(File f) {
+ }
+
+ private File generateHtmlFromDefaultXmlFiles() {
+ final List<File> xmlFiles = getVaildXmlFiles();
+ if (xmlFiles.isEmpty()) {
+ Log.e(TAG, "No notice file exists.");
+ return null;
+ }
+
+ File cachedHtmlFile = getCachedHtmlFile();
+ if (!isCachedHtmlFileOutdated(xmlFiles, cachedHtmlFile)
+ || generateHtmlFile(xmlFiles, cachedHtmlFile)) {
+ return cachedHtmlFile;
+ }
+
+ return null;
+ }
+
+ @VisibleForTesting
+ List<File> getVaildXmlFiles() {
+ final List<File> xmlFiles = new ArrayList();
+ for (final String xmlPath : DEFAULT_LICENSE_XML_PATHS) {
+ File file = new File(xmlPath);
+ if (file.exists() && file.length() != 0) {
+ xmlFiles.add(file);
+ }
+ }
+ return xmlFiles;
+ }
+
+ @VisibleForTesting
+ File getCachedHtmlFile() {
+ return new File(mContext.getCacheDir(), NOTICE_HTML_FILE_NAME);
+ }
+
+ @VisibleForTesting
+ boolean isCachedHtmlFileOutdated(List<File> xmlFiles, File cachedHtmlFile) {
+ boolean outdated = true;
+ if (cachedHtmlFile.exists() && cachedHtmlFile.length() != 0) {
+ outdated = false;
+ for (File file : xmlFiles) {
+ if (cachedHtmlFile.lastModified() < file.lastModified()) {
+ outdated = true;
+ break;
+ }
+ }
+ }
+ return outdated;
+ }
+
+ @VisibleForTesting
+ boolean generateHtmlFile(List<File> xmlFiles, File htmlFile) {
+ return LicenseHtmlGeneratorFromXml.generateHtml(xmlFiles, htmlFile);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoader.java b/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoader.java
new file mode 100644
index 0000000..06770ac
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoader.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.utils;
+
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+
+/**
+ * This class fills in some boilerplate for AsyncTaskLoader to actually load things.
+ *
+ * Subclasses need to implement {@link AsyncLoader#loadInBackground()} to perform the actual
+ * background task, and {@link AsyncLoader#onDiscardResult(T)} to clean up previously loaded
+ * results.
+ *
+ * This loader is based on the MailAsyncTaskLoader from the AOSP EmailUnified repo.
+ *
+ * @param <T> the data type to be loaded.
+ */
+public abstract class AsyncLoader<T> extends AsyncTaskLoader<T> {
+ private T mResult;
+
+ public AsyncLoader(final Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onStartLoading() {
+ if (mResult != null) {
+ deliverResult(mResult);
+ }
+
+ if (takeContentChanged() || mResult == null) {
+ forceLoad();
+ }
+ }
+
+ @Override
+ protected void onStopLoading() {
+ cancelLoad();
+ }
+
+ @Override
+ public void deliverResult(final T data) {
+ if (isReset()) {
+ if (data != null) {
+ onDiscardResult(data);
+ }
+ return;
+ }
+
+ final T oldResult = mResult;
+ mResult = data;
+
+ if (isStarted()) {
+ super.deliverResult(data);
+ }
+
+ if (oldResult != null && oldResult != mResult) {
+ onDiscardResult(oldResult);
+ }
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+
+ onStopLoading();
+
+ if (mResult != null) {
+ onDiscardResult(mResult);
+ }
+ mResult = null;
+ }
+
+ @Override
+ public void onCanceled(final T data) {
+ super.onCanceled(data);
+
+ if (data != null) {
+ onDiscardResult(data);
+ }
+ }
+
+ /**
+ * Called when discarding the load results so subclasses can take care of clean-up or
+ * recycling tasks. This is not called if the same result (by way of pointer equality) is
+ * returned again by a subsequent call to loadInBackground, or if result is null.
+ *
+ * Note that this may be called concurrently with loadInBackground(), and in some circumstances
+ * may be called more than once for a given object.
+ *
+ * @param result The value returned from {@link AsyncLoader#loadInBackground()} which
+ * is to be discarded.
+ */
+ protected abstract void onDiscardResult(T result);
+}
diff --git a/packages/SettingsLib/tests/integ/Android.mk b/packages/SettingsLib/tests/integ/Android.mk
index 7ace048..3c8c8ac 100644
--- a/packages/SettingsLib/tests/integ/Android.mk
+++ b/packages/SettingsLib/tests/integ/Android.mk
@@ -27,6 +27,8 @@
LOCAL_PACKAGE_NAME := SettingsLibTests
LOCAL_COMPATIBILITY_SUITE := device-tests
+LOCAL_USE_AAPT2 := true
+
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
espresso-core \
diff --git a/packages/SettingsLib/tests/robotests/Android.mk b/packages/SettingsLib/tests/robotests/Android.mk
index bc1a834..2738027 100644
--- a/packages/SettingsLib/tests/robotests/Android.mk
+++ b/packages/SettingsLib/tests/robotests/Android.mk
@@ -28,6 +28,8 @@
LOCAL_RESOURCE_DIR := \
$(LOCAL_PATH)/res
+LOCAL_USE_AAPT2 := true
+
include frameworks/base/packages/SettingsLib/common.mk
include $(BUILD_PACKAGE)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index c6cfdb8..976bbee 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -15,20 +15,45 @@
*/
package com.android.settingslib;
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.location.LocationManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.text.TextUtils;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.ArgumentMatchers;
+import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.res.Resources;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowSettings;
@RunWith(SettingsLibRobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+@Config(
+ manifest = TestConfig.MANIFEST_PATH,
+ sdk = TestConfig.SDK_VERSION,
+ shadows = {UtilsTest.ShadowSecure.class})
public class UtilsTest {
private static final double[] TEST_PERCENTAGES = {0, 0.4, 0.5, 0.6, 49, 49.3, 49.8, 50, 100};
private static final String PERCENTAGE_0 = "0%";
@@ -37,6 +62,29 @@
private static final String PERCENTAGE_50 = "50%";
private static final String PERCENTAGE_100 = "100%";
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = spy(RuntimeEnvironment.application);
+ ShadowSecure.reset();
+ }
+
+ @Test
+ public void testUpdateLocationMode_sendBroadcast() {
+ int currentUserId = ActivityManager.getCurrentUser();
+ Utils.updateLocationMode(
+ mContext,
+ Secure.LOCATION_MODE_OFF,
+ Secure.LOCATION_MODE_HIGH_ACCURACY,
+ currentUserId);
+
+ verify(mContext).sendBroadcastAsUser(
+ argThat(actionMatches(LocationManager.MODE_CHANGING_ACTION)),
+ ArgumentMatchers.eq(UserHandle.of(currentUserId)),
+ ArgumentMatchers.eq(WRITE_SECURE_SETTINGS));
+ }
+
@Test
public void testFormatPercentage_RoundTrue_RoundUpIfPossible() {
final String[] expectedPercentages = {PERCENTAGE_0, PERCENTAGE_0, PERCENTAGE_1,
@@ -74,4 +122,23 @@
.thenReturn(60);
assertThat(Utils.getDefaultStorageManagerDaysToRetain(resources)).isEqualTo(60);
}
+
+ private static ArgumentMatcher<Intent> actionMatches(String expected) {
+ return intent -> TextUtils.equals(expected, intent.getAction());
+ }
+
+ @Implements(value = Settings.Secure.class)
+ public static class ShadowSecure extends ShadowSettings.ShadowSecure {
+ private static Map<String, Integer> map = new HashMap<>();
+
+ @Implementation
+ public static boolean putIntForUser(ContentResolver cr, String name, int value, int userHandle) {
+ map.put(name, value);
+ return true;
+ }
+
+ public static void reset() {
+ map.clear();
+ }
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
new file mode 100644
index 0000000..c7e9262
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlGeneratorFromXmlTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.license;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class LicenseHtmlGeneratorFromXmlTest {
+ private static final String VALILD_XML_STRING =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<licenses>\n"
+ + "<file-name contentId=\"0\">/file0</file-name>\n"
+ + "<file-name contentId=\"0\">/file1</file-name>\n"
+ + "<file-content contentId=\"0\"><![CDATA[license content #0]]></file-content>\n"
+ + "</licenses>";
+
+ private static final String INVALILD_XML_STRING =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<licenses2>\n"
+ + "<file-name contentId=\"0\">/file0</file-name>\n"
+ + "<file-name contentId=\"0\">/file1</file-name>\n"
+ + "<file-content contentId=\"0\"><![CDATA[license content #0]]></file-content>\n"
+ + "</licenses2>";
+
+ private static final String EXPECTED_HTML_STRING =
+ "<html><head>\n"
+ + "<style type=\"text/css\">\n"
+ + "body { padding: 0; font-family: sans-serif; }\n"
+ + ".same-license { background-color: #eeeeee;\n"
+ + " border-top: 20px solid white;\n"
+ + " padding: 10px; }\n"
+ + ".label { font-weight: bold; }\n"
+ + ".file-list { margin-left: 1em; color: blue; }\n"
+ + "</style>\n"
+ + "</head>"
+ + "<body topmargin=\"0\" leftmargin=\"0\" rightmargin=\"0\" bottommargin=\"0\">\n"
+ + "<div class=\"toc\">\n"
+ + "<ul>\n"
+ + "<li><a href=\"#id0\">/file0</a></li>\n"
+ + "<li><a href=\"#id0\">/file1</a></li>\n"
+ + "</ul>\n"
+ + "</div><!-- table of contents -->\n"
+ + "<table cellpadding=\"0\" cellspacing=\"0\" border=\"0\">\n"
+ + "<tr id=\"id0\"><td class=\"same-license\">\n"
+ + "<div class=\"label\">Notices for file(s):</div>\n"
+ + "<div class=\"file-list\">\n"
+ + "/file0 <br/>\n"
+ + "/file1 <br/>\n"
+ + "</div><!-- file-list -->\n"
+ + "<pre class=\"license-text\">\n"
+ + "license content #0\n"
+ + "</pre><!-- license-text -->\n"
+ + "</td></tr><!-- same-license -->\n"
+ + "</table></body></html>\n";
+
+ @Test
+ public void testParseValidXmlStream() throws XmlPullParserException, IOException {
+ Map<String, String> fileNameToContentIdMap = new HashMap<String, String>();
+ Map<String, String> contentIdToFileContentMap = new HashMap<String, String>();
+
+ LicenseHtmlGeneratorFromXml.parse(
+ new InputStreamReader(new ByteArrayInputStream(VALILD_XML_STRING.getBytes())),
+ fileNameToContentIdMap, contentIdToFileContentMap);
+ assertThat(fileNameToContentIdMap.size()).isEqualTo(2);
+ assertThat(fileNameToContentIdMap.get("/file0")).isEqualTo("0");
+ assertThat(fileNameToContentIdMap.get("/file1")).isEqualTo("0");
+ assertThat(contentIdToFileContentMap.size()).isEqualTo(1);
+ assertThat(contentIdToFileContentMap.get("0")).isEqualTo("license content #0");
+ }
+
+ @Test(expected = XmlPullParserException.class)
+ public void testParseInvalidXmlStream() throws XmlPullParserException, IOException {
+ Map<String, String> fileNameToContentIdMap = new HashMap<String, String>();
+ Map<String, String> contentIdToFileContentMap = new HashMap<String, String>();
+
+ LicenseHtmlGeneratorFromXml.parse(
+ new InputStreamReader(new ByteArrayInputStream(INVALILD_XML_STRING.getBytes())),
+ fileNameToContentIdMap, contentIdToFileContentMap);
+ }
+
+ @Test
+ public void testGenerateHtml() {
+ Map<String, String> fileNameToContentIdMap = new HashMap<String, String>();
+ Map<String, String> contentIdToFileContentMap = new HashMap<String, String>();
+
+ fileNameToContentIdMap.put("/file0", "0");
+ fileNameToContentIdMap.put("/file1", "0");
+ contentIdToFileContentMap.put("0", "license content #0");
+
+ StringWriter output = new StringWriter();
+ LicenseHtmlGeneratorFromXml.generateHtml(
+ fileNameToContentIdMap, contentIdToFileContentMap, new PrintWriter(output));
+ assertThat(output.toString()).isEqualTo(EXPECTED_HTML_STRING);
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderTest.java
new file mode 100644
index 0000000..1a6f30c
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.license;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import java.io.File;
+import java.util.ArrayList;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class LicenseHtmlLoaderTest {
+ @Mock
+ private Context mContext;
+
+ LicenseHtmlLoader newLicenseHtmlLoader(ArrayList<File> xmlFiles,
+ File cachedHtmlFile, boolean isCachedHtmlFileOutdated,
+ boolean generateHtmlFileSucceeded) {
+ LicenseHtmlLoader loader = spy(new LicenseHtmlLoader(mContext));
+ doReturn(xmlFiles).when(loader).getVaildXmlFiles();
+ doReturn(cachedHtmlFile).when(loader).getCachedHtmlFile();
+ doReturn(isCachedHtmlFileOutdated).when(loader).isCachedHtmlFileOutdated(any(), any());
+ doReturn(generateHtmlFileSucceeded).when(loader).generateHtmlFile(any(), any());
+ return loader;
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testLoadInBackground() {
+ ArrayList<File> xmlFiles = new ArrayList();
+ xmlFiles.add(new File("test.xml"));
+ File cachedHtmlFile = new File("test.html");
+
+ LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, true);
+
+ assertThat(loader.loadInBackground()).isEqualTo(cachedHtmlFile);
+ verify(loader).generateHtmlFile(any(), any());
+ }
+
+ @Test
+ public void testLoadInBackgroundWithNoVaildXmlFiles() {
+ ArrayList<File> xmlFiles = new ArrayList();
+ File cachedHtmlFile = new File("test.html");
+
+ LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, true);
+
+ assertThat(loader.loadInBackground()).isNull();
+ verify(loader, never()).generateHtmlFile(any(), any());
+ }
+
+ @Test
+ public void testLoadInBackgroundWithNonOutdatedCachedHtmlFile() {
+ ArrayList<File> xmlFiles = new ArrayList();
+ xmlFiles.add(new File("test.xml"));
+ File cachedHtmlFile = new File("test.html");
+
+ LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, false, true);
+
+ assertThat(loader.loadInBackground()).isEqualTo(cachedHtmlFile);
+ verify(loader, never()).generateHtmlFile(any(), any());
+ }
+
+ @Test
+ public void testLoadInBackgroundWithGenerateHtmlFileFailed() {
+ ArrayList<File> xmlFiles = new ArrayList();
+ xmlFiles.add(new File("test.xml"));
+ File cachedHtmlFile = new File("test.html");
+
+ LicenseHtmlLoader loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, false);
+
+ assertThat(loader.loadInBackground()).isNull();
+ verify(loader).generateHtmlFile(any(), any());
+ }
+}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ca85445..659c5a8 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1989,8 +1989,8 @@
<!-- Action label for launching app info on the specified app [CHAR LIMIT=20] -->
<string name="app_info">App info</string>
- <!-- Action label for switching to web for an instant app [CHAR LIMIT=20] -->
- <string name="go_to_web">Go to web</string>
+ <!-- Action label for switching to a browser for an instant app [CHAR LIMIT=20] -->
+ <string name="go_to_web">Go to browser</string>
<!-- Quick settings tile for toggling mobile data [CHAR LIMIT=20] -->
<string name="mobile_data">Mobile data</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
index 17cb0d8..7db3ac6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
@@ -43,6 +43,9 @@
public void onActivityDismissingDockedStack() { }
public void onActivityLaunchOnSecondaryDisplayFailed() { }
public void onTaskProfileLocked(int taskId, int userId) { }
+ public void onTaskRemoved(int taskId) { }
+ public void onTaskMovedToFront(int taskId) { }
+ public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) { }
/**
* Checks that the current user matches the process. Since
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index 81c37a9..857e0ea 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -145,6 +145,23 @@
mHandler.obtainMessage(H.ON_TASK_SNAPSHOT_CHANGED, taskId, 0, snapshot).sendToTarget();
}
+ @Override
+ public void onTaskRemoved(int taskId) throws RemoteException {
+ mHandler.obtainMessage(H.ON_TASK_REMOVED, taskId, 0).sendToTarget();
+ }
+
+ @Override
+ public void onTaskMovedToFront(int taskId) throws RemoteException {
+ mHandler.obtainMessage(H.ON_TASK_MOVED_TO_FRONT, taskId, 0).sendToTarget();
+ }
+
+ @Override
+ public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation)
+ throws RemoteException {
+ mHandler.obtainMessage(H.ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE, taskId,
+ requestedOrientation).sendToTarget();
+ }
+
private final class H extends Handler {
private static final int ON_TASK_STACK_CHANGED = 1;
private static final int ON_TASK_SNAPSHOT_CHANGED = 2;
@@ -157,6 +174,10 @@
private static final int ON_PINNED_STACK_ANIMATION_STARTED = 9;
private static final int ON_ACTIVITY_UNPINNED = 10;
private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED = 11;
+ private static final int ON_TASK_REMOVED = 12;
+ private static final int ON_TASK_MOVED_TO_FRONT = 13;
+ private static final int ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE = 14;
+
public H(Looper looper) {
super(looper);
@@ -241,6 +262,25 @@
}
break;
}
+ case ON_TASK_REMOVED: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onTaskRemoved(msg.arg1);
+ }
+ break;
+ }
+ case ON_TASK_MOVED_TO_FRONT: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onTaskMovedToFront(msg.arg1);
+ }
+ break;
+ }
+ case ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE: {
+ for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+ mTaskStackListeners.get(i).onActivityRequestedOrientationChanged(
+ msg.arg1, msg.arg2);
+ }
+ break;
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
index 5ffd785..8d50d4b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
@@ -45,6 +45,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.keyguard.KeyguardStatusView;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.Utils;
import com.android.settingslib.drawable.UserIconDrawable;
import com.android.systemui.Dependency;
@@ -431,7 +432,7 @@
@Override
public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
if (picture != null &&
- UserManager.get(mContext).isGuestUser(ActivityManager.getCurrentUser()) &&
+ UserManager.get(mContext).isGuestUser(KeyguardUpdateMonitor.getCurrentUser()) &&
!(picture instanceof UserIconDrawable)) {
picture = picture.getConstantState().newDrawable(mContext.getResources()).mutate();
picture.setColorFilter(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 4a91ee0..cdf0c0f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -63,6 +63,7 @@
private QSContainerImpl mContainer;
private int mLayoutDirection;
private QSFooter mFooter;
+ private float mLastQSExpansion = -1;
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@@ -227,6 +228,7 @@
public void setKeyguardShowing(boolean keyguardShowing) {
if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
mKeyguardShowing = keyguardShowing;
+ mLastQSExpansion = -1;
if (mQSAnimator != null) {
mQSAnimator.setOnKeyguard(keyguardShowing);
@@ -268,6 +270,10 @@
getView().setTranslationY(mKeyguardShowing ? (translationScaleY * height)
: headerTranslation);
}
+ if (expansion == mLastQSExpansion) {
+ return;
+ }
+ mLastQSExpansion = expansion;
mHeader.setExpansion(mKeyguardShowing ? 1 : expansion);
mFooter.setExpansion(mKeyguardShowing ? 1 : expansion);
int heightDiff = mQSPanel.getBottom() - mHeader.getBottom() + mHeader.getPaddingBottom()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
index b3d6e32..db19d2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -80,10 +80,21 @@
private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
- Path clipPath = getClipPath();
- if (clipPath != null && clipPath.isConvex()) {
- // The path might not be convex in border cases where the view is small and clipped
- outline.setConvexPath(clipPath);
+ if (!mCustomOutline && mCurrentTopRoundness == 0.0f
+ && mCurrentBottomRoundness == 0.0f && !mAlwaysRoundBothCorners) {
+ int translation = mShouldTranslateContents ? (int) getTranslation() : 0;
+ int left = Math.max(translation + mCurrentSidePaddings, mCurrentSidePaddings);
+ int top = mClipTopAmount + mBackgroundTop;
+ int right = getWidth() - mCurrentSidePaddings + Math.min(translation, 0);
+ int bottom = Math.max(getActualHeight() - mClipBottomAmount, top);
+ outline.setRect(left, top, right, bottom);
+ } else {
+ Path clipPath = getClipPath();
+ if (clipPath != null && clipPath.isConvex()) {
+ // The path might not be convex in border cases where the view is small and
+ // clipped
+ outline.setConvexPath(clipPath);
+ }
}
outline.setAlpha(mOutlineAlpha);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 99debee..c281379 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -102,7 +102,7 @@
return;
}
- final int activeUserId = ActivityManager.getCurrentUser();
+ final int activeUserId = KeyguardUpdateMonitor.getCurrentUser();
final boolean isSystemUser =
UserManager.isSplitSystemUser() && activeUserId == UserHandle.USER_SYSTEM;
final boolean allowDismissKeyguard = !isSystemUser && activeUserId == keyguardUserId;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 59533e6..c967423 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -3694,23 +3694,35 @@
* See also StatusBar.setPanelExpanded for another place where we attempt to do this.
*/
private void handleVisibleToUserChangedImpl(boolean visibleToUser) {
- try {
- if (visibleToUser) {
- boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
- boolean clearNotificationEffects =
- !isPresenterFullyCollapsed() &&
- (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED);
- int notificationLoad = mNotificationData.getActiveNotifications().size();
- if (pinnedHeadsUp && isPresenterFullyCollapsed()) {
- notificationLoad = 1;
- }
- mBarService.onPanelRevealed(clearNotificationEffects, notificationLoad);
- } else {
- mBarService.onPanelHidden();
+ if (visibleToUser) {
+ boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
+ boolean clearNotificationEffects =
+ !isPresenterFullyCollapsed() &&
+ (mState == StatusBarState.SHADE
+ || mState == StatusBarState.SHADE_LOCKED);
+ int notificationLoad = mNotificationData.getActiveNotifications().size();
+ if (pinnedHeadsUp && isPresenterFullyCollapsed()) {
+ notificationLoad = 1;
}
- } catch (RemoteException ex) {
- // Won't fail unless the world has ended.
+ final int finalNotificationLoad = notificationLoad;
+ mUiOffloadThread.submit(() -> {
+ try {
+ mBarService.onPanelRevealed(clearNotificationEffects,
+ finalNotificationLoad);
+ } catch (RemoteException ex) {
+ // Won't fail unless the world has ended.
+ }
+ });
+ } else {
+ mUiOffloadThread.submit(() -> {
+ try {
+ mBarService.onPanelHidden();
+ } catch (RemoteException ex) {
+ // Won't fail unless the world has ended.
+ }
+ });
}
+
}
private void stopNotificationLogging() {
@@ -3746,21 +3758,23 @@
newlyVisible.toArray(new NotificationVisibility[newlyVisible.size()]);
NotificationVisibility[] noLongerVisibleAr =
noLongerVisible.toArray(new NotificationVisibility[noLongerVisible.size()]);
- try {
- mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr);
- } catch (RemoteException e) {
- // Ignore.
- }
-
- final int N = newlyVisible.size();
- if (N > 0) {
- String[] newlyVisibleKeyAr = new String[N];
- for (int i = 0; i < N; i++) {
- newlyVisibleKeyAr[i] = newlyVisibleAr[i].key;
+ mUiOffloadThread.submit(() -> {
+ try {
+ mBarService.onNotificationVisibilityChanged(newlyVisibleAr, noLongerVisibleAr);
+ } catch (RemoteException e) {
+ // Ignore.
}
- setNotificationsShown(newlyVisibleKeyAr);
- }
+ final int N = newlyVisible.size();
+ if (N > 0) {
+ String[] newlyVisibleKeyAr = new String[N];
+ for (int i = 0; i < N; i++) {
+ newlyVisibleKeyAr[i] = newlyVisibleAr[i].key;
+ }
+
+ setNotificationsShown(newlyVisibleKeyAr);
+ }
+ });
}
// State logging
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 874f0d9..4ee4ef4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -39,6 +39,8 @@
import java.util.ArrayList;
import java.util.List;
+import static com.android.settingslib.Utils.updateLocationMode;
+
/**
* A controller to manage changes of location related states and update the views accordingly.
*/
@@ -106,12 +108,13 @@
final ContentResolver cr = mContext.getContentResolver();
// When enabling location, a user consent dialog will pop up, and the
// setting won't be fully enabled until the user accepts the agreement.
+ int currentMode = Settings.Secure.getIntForUser(cr, Settings.Secure.LOCATION_MODE,
+ Settings.Secure.LOCATION_MODE_OFF, currentUserId);
int mode = enabled
? Settings.Secure.LOCATION_MODE_PREVIOUS : Settings.Secure.LOCATION_MODE_OFF;
// QuickSettings always runs as the owner, so specifically set the settings
// for the current foreground user.
- return Settings.Secure
- .putIntForUser(cr, Settings.Secure.LOCATION_MODE, mode, currentUserId);
+ return updateLocationMode(mContext, currentMode, mode, currentUserId);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
index 722874b..f258fb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
@@ -24,6 +24,7 @@
boolean isRotationLockAffordanceVisible();
boolean isRotationLocked();
void setRotationLocked(boolean locked);
+ void setRotationLockedAtAngle(boolean locked, int rotation);
public interface RotationLockControllerCallback {
void onRotationLockStateChanged(boolean rotationLocked, boolean affordanceVisible);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
index 4f96496..5418dc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
@@ -63,6 +63,10 @@
RotationPolicy.setRotationLock(mContext, locked);
}
+ public void setRotationLockedAtAngle(boolean locked, int rotation){
+ RotationPolicy.setRotationLockAtAngle(mContext, locked, rotation);
+ }
+
public boolean isRotationLockAffordanceVisible() {
return RotationPolicy.isRotationLockToggleVisible(mContext);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 0aeb7b6..5ff90d91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -430,6 +430,7 @@
public void testLogHidden() {
try {
mStatusBar.handleVisibleToUserChanged(false);
+ waitForUiOffloadThread();
verify(mBarService, times(1)).onPanelHidden();
verify(mBarService, never()).onPanelRevealed(anyBoolean(), anyInt());
} catch (RemoteException e) {
@@ -447,7 +448,7 @@
try {
mStatusBar.handleVisibleToUserChanged(true);
-
+ waitForUiOffloadThread();
verify(mBarService, never()).onPanelHidden();
verify(mBarService, times(1)).onPanelRevealed(false, 1);
} catch (RemoteException e) {
@@ -466,7 +467,7 @@
try {
mStatusBar.handleVisibleToUserChanged(true);
-
+ waitForUiOffloadThread();
verify(mBarService, never()).onPanelHidden();
verify(mBarService, times(1)).onPanelRevealed(true, 5);
} catch (RemoteException e) {
@@ -485,7 +486,7 @@
try {
mStatusBar.handleVisibleToUserChanged(true);
-
+ waitForUiOffloadThread();
verify(mBarService, never()).onPanelHidden();
verify(mBarService, times(1)).onPanelRevealed(false, 5);
} catch (RemoteException e) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
index d60fe78..be11024 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
@@ -49,4 +49,9 @@
public void setRotationLocked(boolean locked) {
}
+
+ @Override
+ public void setRotationLockedAtAngle(boolean locked, int rotation) {
+
+ }
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 5d4ddf0..b2a0ce5 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -715,14 +715,14 @@
@Nullable ArrayList<String> changedDatasetIds,
@Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
@Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
- @NonNull ArrayList<AutofillId> detectedFieldIdsList,
- @NonNull ArrayList<Match> detectedMatchesList) {
+ @Nullable ArrayList<AutofillId> detectedFieldIdsList,
+ @Nullable ArrayList<Match> detectedMatchesList) {
synchronized (mLock) {
if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
AutofillId[] detectedFieldsIds = null;
Match[] detectedMatches = null;
- if (!detectedFieldIdsList.isEmpty()) {
+ if (detectedFieldIdsList != null) {
detectedFieldsIds = new AutofillId[detectedFieldIdsList.size()];
detectedFieldIdsList.toArray(detectedFieldsIds);
detectedMatches = new Match[detectedMatchesList.size()];
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index fc57a0d..dc35051 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -108,6 +108,8 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
import android.util.ArrayMap;
@@ -189,6 +191,7 @@
static final int MSG_CREATE_SESSION = 1050;
static final int MSG_START_INPUT = 2000;
+ static final int MSG_START_VR_INPUT = 2010;
static final int MSG_UNBIND_CLIENT = 3000;
static final int MSG_BIND_CLIENT = 3010;
@@ -317,6 +320,28 @@
}
}
+ /**
+ * VR state callback.
+ * Listens for when VR mode finishes.
+ */
+ private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
+ @Override
+ public void onVrStateChanged(boolean enabled) {
+ if (!enabled) {
+ restoreNonVrImeFromSettingsNoCheck();
+ }
+ }
+ };
+
+ private void restoreNonVrImeFromSettingsNoCheck() {
+ // switch back to non-VR InputMethod from settings.
+ synchronized (mMethodMap) {
+ final String lastInputId = mSettings.getSelectedInputMethod();
+ setInputMethodLocked(lastInputId,
+ mSettings.getSelectedInputMethodSubtypeId(lastInputId));
+ }
+ }
+
static final class ClientState {
final IInputMethodClient client;
final IInputContext inputContext;
@@ -863,6 +888,30 @@
}
/**
+ * Start a VR InputMethod that matches IME with package name of {@param component}.
+ * Note: This method is called from {@link VrManager}.
+ */
+ private void startVrInputMethodNoCheck(@Nullable ComponentName component) {
+ if (component == null) {
+ // clear the current VR-only IME (if any) and restore normal IME.
+ restoreNonVrImeFromSettingsNoCheck();
+ return;
+ }
+
+ synchronized (mMethodMap) {
+ String packageName = component.getPackageName();
+ for (InputMethodInfo info : mMethodList) {
+ if (TextUtils.equals(info.getPackageName(), packageName) && info.isVrOnly()) {
+ // set this is as current inputMethod without updating settings.
+ setInputMethodEnabled(info.getId(), true);
+ setInputMethodLocked(info.getId(), NOT_A_SUBTYPE_ID);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
* Handles {@link Intent#ACTION_LOCALE_CHANGED}.
*
* <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all
@@ -1338,6 +1387,15 @@
mFileManager = new InputMethodFileManager(mMethodMap, userId);
mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
mSettings, context);
+ // Register VR-state listener.
+ IVrManager vrManager = (IVrManager) ServiceManager.getService(Context.VR_SERVICE);
+ if (vrManager != null) {
+ try {
+ vrManager.registerListener(mVrStateCallbacks);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register VR mode state listener.");
+ }
+ }
}
private void resetDefaultImeLocked(Context context) {
@@ -1562,12 +1620,27 @@
@Override
public List<InputMethodInfo> getInputMethodList() {
+ return getInputMethodList(false /* isVrOnly */);
+ }
+
+ public List<InputMethodInfo> getVrInputMethodList() {
+ return getInputMethodList(true /* isVrOnly */);
+ }
+
+ private List<InputMethodInfo> getInputMethodList(final boolean isVrOnly) {
// TODO: Make this work even for non-current users?
if (!calledFromValidUser()) {
return Collections.emptyList();
}
synchronized (mMethodMap) {
- return new ArrayList<>(mMethodList);
+ ArrayList<InputMethodInfo> methodList = new ArrayList<>();
+ for (InputMethodInfo info : mMethodList) {
+
+ if (info.isVrOnly() == isVrOnly) {
+ methodList.add(info);
+ }
+ }
+ return methodList;
}
}
@@ -3356,6 +3429,9 @@
case MSG_SET_INTERACTIVE:
handleSetInteractive(msg.arg1 != 0);
return true;
+ case MSG_START_VR_INPUT:
+ startVrInputMethodNoCheck((ComponentName) msg.obj);
+ return true;
case MSG_SWITCH_IME:
handleSwitchInputMethod(msg.arg1 != 0);
return true;
@@ -3876,8 +3952,12 @@
private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
boolean setSubtypeOnly) {
- // Update the history of InputMethod and Subtype
- mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
+ // Updates to InputMethod are transient in VR mode. Its not included in history.
+ final boolean isVrInput = imi != null && imi.isVrOnly();
+ if (!isVrInput) {
+ // Update the history of InputMethod and Subtype
+ mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
+ }
mCurUserActionNotificationSequenceNumber =
Math.max(mCurUserActionNotificationSequenceNumber + 1, 1);
@@ -3892,6 +3972,11 @@
mCurUserActionNotificationSequenceNumber, mCurClient));
}
+ if (isVrInput) {
+ // Updates to InputMethod are transient in VR mode. Any changes to Settings are skipped.
+ return;
+ }
+
// Set Subtype here
if (imi == null || subtypeId < 0) {
mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
@@ -4351,6 +4436,11 @@
mHandler.removeMessages(MSG_HIDE_CURRENT_INPUT_METHOD);
mHandler.sendEmptyMessage(MSG_HIDE_CURRENT_INPUT_METHOD);
}
+
+ @Override
+ public void startVrInputMethodNoCheck(@Nullable ComponentName componentName) {
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_START_VR_INPUT, componentName));
+ }
}
private static String imeWindowStatusToString(final int imeWindowVis) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2ca2c27..7b4703a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8877,7 +8877,10 @@
final int callingAppId = UserHandle.getAppId(callingUid);
if ((callingAppId == SYSTEM_UID) || (callingAppId == ROOT_UID)) {
if ("com.android.settings.files".equals(grantUri.uri.getAuthority())) {
- // Exempted authority for cropping user photos in Settings app
+ // Exempted authority for
+ // 1. cropping user photos and sharing a generated license html
+ // file in Settings app
+ // 2. sharing a generated license html file in TvSettings app
} else {
Slog.w(TAG, "For security reasons, the system cannot issue a Uri permission"
+ " grant to " + grantUri + "; use startActivityAsCaller() instead");
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 9cd52d7..bdfd4bd 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -323,7 +323,7 @@
private final BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- EndPoint target = new EndPoint(null, null, context.getUserId());
+ EndPoint target = new EndPoint(null, null, getSendingUserId());
updateRunningAccounts(target /* sync targets for user */);
}
};
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8b2854c..f60cb06 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -9714,6 +9714,7 @@
// them in the case where we're not upgrading or booting for the first time.
String primaryCpuAbiFromSettings = null;
String secondaryCpuAbiFromSettings = null;
+ boolean needToDeriveAbi = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0;
// writer
synchronized (mPackages) {
@@ -9791,11 +9792,14 @@
}
}
- if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) == 0) {
+ if (!needToDeriveAbi) {
PackageSetting foundPs = mSettings.getPackageLPr(pkg.packageName);
if (foundPs != null) {
primaryCpuAbiFromSettings = foundPs.primaryCpuAbiString;
secondaryCpuAbiFromSettings = foundPs.secondaryCpuAbiString;
+ } else {
+ // when re-adding a system package failed after uninstalling updates.
+ needToDeriveAbi = true;
}
}
@@ -10011,7 +10015,7 @@
final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting);
if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
- if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) {
+ if (needToDeriveAbi) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
final boolean extractNativeLibs = !pkg.isLibrary();
derivePackageAbi(pkg, cpuAbiOverride, extractNativeLibs, mAppLib32InstallDir);
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 1f4e64e..7d55b68 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -59,6 +59,7 @@
import android.util.Slog;
import android.util.SparseArray;
import com.android.server.wm.WindowManagerInternal;
+import android.view.inputmethod.InputMethodManagerInternal;
import com.android.internal.R;
import com.android.internal.util.DumpUtils;
@@ -609,6 +610,14 @@
}
@Override
+ public void setVrInputMethod(ComponentName componentName) {
+ enforceCallerPermissionAnyOf(Manifest.permission.RESTRICTED_VR_ACCESS);
+ InputMethodManagerInternal imm =
+ LocalServices.getService(InputMethodManagerInternal.class);
+ imm.startVrInputMethodNoCheck(componentName);
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 16150ba..2091101 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -416,8 +416,15 @@
*/
public static final int PROPERTY_SELF_MANAGED = 0x00000100;
+ /**
+ * Indicates the call used Assisted Dialing.
+ * See also {@link Connection#PROPERTY_ASSISTED_DIALING_USED}
+ * @hide
+ */
+ public static final int PROPERTY_ASSISTED_DIALING_USED = 0x00000200;
+
//******************************************************************************************
- // Next PROPERTY value: 0x00000200
+ // Next PROPERTY value: 0x00000400
//******************************************************************************************
private final String mTelecomCallId;
@@ -577,6 +584,9 @@
if(hasProperty(properties, PROPERTY_HAS_CDMA_VOICE_PRIVACY)) {
builder.append(" PROPERTY_HAS_CDMA_VOICE_PRIVACY");
}
+ if(hasProperty(properties, PROPERTY_ASSISTED_DIALING_USED)) {
+ builder.append(" PROPERTY_ASSISTED_DIALING_USED");
+ }
builder.append("]");
return builder.toString();
}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 3c32614..aaef8d3 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -399,8 +399,14 @@
*/
public static final int PROPERTY_IS_RTT = 1 << 8;
+ /**
+ * Set by the framework to indicate that a connection is using assisted dialing.
+ * @hide
+ */
+ public static final int PROPERTY_ASSISTED_DIALING_USED = 1 << 9;
+
//**********************************************************************************************
- // Next PROPERTY value: 1<<9
+ // Next PROPERTY value: 1<<10
//**********************************************************************************************
/**
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 6dcc3da..15355ac 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -590,6 +590,14 @@
ComponentName.createRelative("com.android.phone", ".EmergencyDialer");
/**
+ * The boolean indicated by this extra controls whether or not a call is eligible to undergo
+ * assisted dialing. This extra is stored under {@link #EXTRA_OUTGOING_CALL_EXTRAS}.
+ * @hide
+ */
+ public static final String EXTRA_USE_ASSISTED_DIALING =
+ "android.telecom.extra.USE_ASSISTED_DIALING";
+
+ /**
* The following 4 constants define how properties such as phone numbers and names are
* displayed to the user.
*/
diff --git a/wifi/java/android/net/wifi/rtt/RangingRequest.java b/wifi/java/android/net/wifi/rtt/RangingRequest.java
index 7d74a72..b4e3097 100644
--- a/wifi/java/android/net/wifi/rtt/RangingRequest.java
+++ b/wifi/java/android/net/wifi/rtt/RangingRequest.java
@@ -17,6 +17,7 @@
package android.net.wifi.rtt;
import android.annotation.NonNull;
+import android.net.MacAddress;
import android.net.wifi.ScanResult;
import android.net.wifi.aware.AttachCallback;
import android.net.wifi.aware.DiscoverySessionCallback;
@@ -168,7 +169,7 @@
* @param peerMacAddress The MAC address of the Wi-Fi Aware peer.
* @return The builder, to facilitate chaining {@code builder.setXXX(..).setXXX(..)}.
*/
- public Builder addWifiAwarePeer(@NonNull byte[] peerMacAddress) {
+ public Builder addWifiAwarePeer(@NonNull MacAddress peerMacAddress) {
if (peerMacAddress == null) {
throw new IllegalArgumentException("Null peer MAC address");
}
diff --git a/wifi/java/android/net/wifi/rtt/RangingResult.java b/wifi/java/android/net/wifi/rtt/RangingResult.java
index 93e52ae..a380fae 100644
--- a/wifi/java/android/net/wifi/rtt/RangingResult.java
+++ b/wifi/java/android/net/wifi/rtt/RangingResult.java
@@ -17,16 +17,15 @@
package android.net.wifi.rtt;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.net.MacAddress;
import android.net.wifi.aware.PeerHandle;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
-import libcore.util.HexEncoding;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -62,7 +61,7 @@
public static final int STATUS_FAIL = 1;
private final int mStatus;
- private final byte[] mMac;
+ private final MacAddress mMac;
private final PeerHandle mPeerHandle;
private final int mDistanceMm;
private final int mDistanceStdDevMm;
@@ -70,7 +69,7 @@
private final long mTimestamp;
/** @hide */
- public RangingResult(@RangeResultStatus int status, byte[] mac, int distanceMm,
+ public RangingResult(@RangeResultStatus int status, @NonNull MacAddress mac, int distanceMm,
int distanceStdDevMm, int rssi, long timestamp) {
mStatus = status;
mMac = mac;
@@ -109,7 +108,7 @@
* Will return a {@code null} for results corresponding to requests issued using a {@code
* PeerHandle}, i.e. using the {@link RangingRequest.Builder#addWifiAwarePeer(PeerHandle)} API.
*/
- public byte[] getMacAddress() {
+ public MacAddress getMacAddress() {
return mMac;
}
@@ -193,7 +192,12 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mStatus);
- dest.writeByteArray(mMac);
+ if (mMac == null) {
+ dest.writeBoolean(false);
+ } else {
+ dest.writeBoolean(true);
+ mMac.writeToParcel(dest, flags);
+ }
if (mPeerHandle == null) {
dest.writeBoolean(false);
} else {
@@ -216,7 +220,11 @@
@Override
public RangingResult createFromParcel(Parcel in) {
int status = in.readInt();
- byte[] mac = in.createByteArray();
+ boolean macAddressPresent = in.readBoolean();
+ MacAddress mac = null;
+ if (macAddressPresent) {
+ mac = MacAddress.CREATOR.createFromParcel(in);
+ }
boolean peerHandlePresent = in.readBoolean();
PeerHandle peerHandle = null;
if (peerHandlePresent) {
@@ -240,11 +248,11 @@
@Override
public String toString() {
return new StringBuilder("RangingResult: [status=").append(mStatus).append(", mac=").append(
- mMac == null ? "<null>" : new String(HexEncoding.encodeToString(mMac))).append(
- ", peerHandle=").append(mPeerHandle == null ? "<null>" : mPeerHandle.peerId).append(
- ", distanceMm=").append(mDistanceMm).append(", distanceStdDevMm=").append(
- mDistanceStdDevMm).append(", rssi=").append(mRssi).append(", timestamp=").append(
- mTimestamp).append("]").toString();
+ mMac).append(", peerHandle=").append(
+ mPeerHandle == null ? "<null>" : mPeerHandle.peerId).append(", distanceMm=").append(
+ mDistanceMm).append(", distanceStdDevMm=").append(mDistanceStdDevMm).append(
+ ", rssi=").append(mRssi).append(", timestamp=").append(mTimestamp).append(
+ "]").toString();
}
@Override
@@ -259,7 +267,7 @@
RangingResult lhs = (RangingResult) o;
- return mStatus == lhs.mStatus && Arrays.equals(mMac, lhs.mMac) && Objects.equals(
+ return mStatus == lhs.mStatus && Objects.equals(mMac, lhs.mMac) && Objects.equals(
mPeerHandle, lhs.mPeerHandle) && mDistanceMm == lhs.mDistanceMm
&& mDistanceStdDevMm == lhs.mDistanceStdDevMm && mRssi == lhs.mRssi
&& mTimestamp == lhs.mTimestamp;
diff --git a/wifi/java/android/net/wifi/rtt/ResponderConfig.java b/wifi/java/android/net/wifi/rtt/ResponderConfig.java
index 1090bfa..8be7782 100644
--- a/wifi/java/android/net/wifi/rtt/ResponderConfig.java
+++ b/wifi/java/android/net/wifi/rtt/ResponderConfig.java
@@ -24,11 +24,8 @@
import android.os.Parcel;
import android.os.Parcelable;
-import libcore.util.HexEncoding;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Arrays;
import java.util.Objects;
/**
@@ -127,13 +124,13 @@
* peerHandle field) ise used to identify the Responder.
* TODO: convert to MacAddress
*/
- public byte[] macAddress;
+ public MacAddress macAddress;
/**
* The peer identifier of a Wi-Fi Aware Responder. Will be null if a MAC Address (the macAddress
* field) is used to identify the Responder.
*/
- public final PeerHandle peerHandle;
+ public PeerHandle peerHandle;
/**
* The device type of the Responder.
@@ -194,9 +191,13 @@
* @param preamble The preamble used by the Responder, specified using
* {@link PreambleType}.
*/
- public ResponderConfig(@NonNull byte[] macAddress, @ResponderType int responderType,
+ public ResponderConfig(@NonNull MacAddress macAddress, @ResponderType int responderType,
boolean supports80211mc, @ChannelWidth int channelWidth, int frequency, int centerFreq0,
int centerFreq1, @PreambleType int preamble) {
+ if (macAddress == null) {
+ throw new IllegalArgumentException(
+ "Invalid ResponderConfig - must specify a MAC address");
+ }
this.macAddress = macAddress;
this.peerHandle = null;
this.responderType = responderType;
@@ -248,10 +249,7 @@
* Point (AP), which can be obtained from {@link android.net.wifi.WifiManager#getScanResults()}.
*/
public static ResponderConfig fromScanResult(ScanResult scanResult) {
- byte[] macAddress = null;
- if (scanResult.BSSID != null) {
- macAddress = MacAddress.byteAddrFromStringAddr(scanResult.BSSID);
- }
+ MacAddress macAddress = MacAddress.fromString(scanResult.BSSID);
int responderType = RESPONDER_AP;
boolean supports80211mc = scanResult.is80211mcResponder();
int channelWidth = translcateScanResultChannelWidth(scanResult.channelWidth);
@@ -275,7 +273,7 @@
* Creates a Responder configuration from a MAC address corresponding to a Wi-Fi Aware
* Responder. The Responder parameters are set to defaults.
*/
- public static ResponderConfig fromWifiAwarePeerMacAddressWithDefaults(byte[] macAddress) {
+ public static ResponderConfig fromWifiAwarePeerMacAddressWithDefaults(MacAddress macAddress) {
/* Note: the parameters are those of the Aware discovery channel (channel 6). A Responder
* is expected to be brought up and available to negotiate a maximum accuracy channel
* (i.e. Band 5 @ 80MHz). A Responder is brought up on the peer by starting an Aware
@@ -323,11 +321,16 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeByteArray(macAddress);
- if (peerHandle == null) {
- dest.writeInt(0);
+ if (macAddress == null) {
+ dest.writeBoolean(false);
} else {
- dest.writeInt(1);
+ dest.writeBoolean(true);
+ macAddress.writeToParcel(dest, flags);
+ }
+ if (peerHandle == null) {
+ dest.writeBoolean(false);
+ } else {
+ dest.writeBoolean(true);
dest.writeInt(peerHandle.peerId);
}
dest.writeInt(responderType);
@@ -347,10 +350,14 @@
@Override
public ResponderConfig createFromParcel(Parcel in) {
- byte[] macAddress = in.createByteArray();
- int peerHandleFlag = in.readInt();
+ boolean macAddressPresent = in.readBoolean();
+ MacAddress macAddress = null;
+ if (macAddressPresent) {
+ macAddress = MacAddress.CREATOR.createFromParcel(in);
+ }
+ boolean peerHandlePresent = in.readBoolean();
PeerHandle peerHandle = null;
- if (peerHandleFlag == 1) {
+ if (peerHandlePresent) {
peerHandle = new PeerHandle(in.readInt());
}
int responderType = in.readInt();
@@ -383,7 +390,7 @@
ResponderConfig lhs = (ResponderConfig) o;
- return Arrays.equals(macAddress, lhs.macAddress) && Objects.equals(peerHandle,
+ return Objects.equals(macAddress, lhs.macAddress) && Objects.equals(peerHandle,
lhs.peerHandle) && responderType == lhs.responderType
&& supports80211mc == lhs.supports80211mc && channelWidth == lhs.channelWidth
&& frequency == lhs.frequency && centerFreq0 == lhs.centerFreq0
@@ -399,8 +406,7 @@
/** @hide */
@Override
public String toString() {
- return new StringBuffer("ResponderConfig: macAddress=").append(
- macAddress == null ? "<null>" : new String(HexEncoding.encode(macAddress))).append(
+ return new StringBuffer("ResponderConfig: macAddress=").append(macAddress).append(
", peerHandle=").append(peerHandle == null ? "<null>" : peerHandle.peerId).append(
", responderType=").append(responderType).append(", supports80211mc=").append(
supports80211mc).append(", channelWidth=").append(channelWidth).append(
diff --git a/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
index 300d425..72e95b9 100644
--- a/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/rtt/WifiRttManagerTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.net.MacAddress;
import android.net.wifi.ScanResult;
import android.net.wifi.aware.PeerHandle;
import android.os.Handler;
@@ -33,8 +34,6 @@
import android.os.test.TestLooper;
import android.test.suitebuilder.annotation.SmallTest;
-import libcore.util.HexEncoding;
-
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@@ -73,13 +72,15 @@
}
/**
- * Validate ranging call flow with succesful results.
+ * Validate ranging call flow with successful results.
*/
@Test
public void testRangeSuccess() throws Exception {
RangingRequest request = new RangingRequest.Builder().build();
List<RangingResult> results = new ArrayList<>();
- results.add(new RangingResult(RangingResult.STATUS_SUCCESS, (byte[]) null, 15, 5, 10, 666));
+ results.add(
+ new RangingResult(RangingResult.STATUS_SUCCESS, MacAddress.BROADCAST_ADDRESS, 15, 5,
+ 10, 666));
RangingResultCallback callbackMock = mock(RangingResultCallback.class);
ArgumentCaptor<IRttCallback> callbackCaptor = ArgumentCaptor.forClass(IRttCallback.class);
@@ -135,7 +136,7 @@
List<ScanResult> scanResults2and3 = new ArrayList<>(2);
scanResults2and3.add(scanResult2);
scanResults2and3.add(scanResult3);
- final byte[] mac1 = HexEncoding.decode("000102030405".toCharArray(), false);
+ MacAddress mac1 = MacAddress.fromString("00:01:02:03:04:05");
PeerHandle peerHandle1 = new PeerHandle(12);
RangingRequest.Builder builder = new RangingRequest.Builder();
@@ -169,7 +170,7 @@
for (int i = 0; i < RangingRequest.getMaxPeers() - 3; ++i) {
scanResultList.add(scanResult);
}
- final byte[] mac1 = HexEncoding.decode("000102030405".toCharArray(), false);
+ MacAddress mac1 = MacAddress.fromString("00:01:02:03:04:05");
// create request
RangingRequest.Builder builder = new RangingRequest.Builder();
@@ -189,11 +190,12 @@
@Test(expected = IllegalArgumentException.class)
public void testRangingRequestPastLimit() {
ScanResult scanResult = new ScanResult();
+ scanResult.BSSID = "00:01:02:03:04:05";
List<ScanResult> scanResultList = new ArrayList<>();
for (int i = 0; i < RangingRequest.getMaxPeers() - 2; ++i) {
scanResultList.add(scanResult);
}
- final byte[] mac1 = HexEncoding.decode("000102030405".toCharArray(), false);
+ MacAddress mac1 = MacAddress.fromString("00:01:02:03:04:05");
// create request
RangingRequest.Builder builder = new RangingRequest.Builder();
@@ -228,7 +230,7 @@
public void testRangingResultsParcel() {
// Note: not validating parcel code of ScanResult (assumed to work)
int status = RangingResult.STATUS_SUCCESS;
- final byte[] mac = HexEncoding.decode("000102030405".toCharArray(), false);
+ final MacAddress mac = MacAddress.fromString("00:01:02:03:04:05");
PeerHandle peerHandle = new PeerHandle(10);
int distanceCm = 105;
int distanceStdDevCm = 10;