Merge "Add metrics for nav buttons through KeyButtonView" into oc-dev
diff --git a/Android.mk b/Android.mk
index 01fb73e..915f103 100644
--- a/Android.mk
+++ b/Android.mk
@@ -321,6 +321,8 @@
core/java/android/service/wallpaper/IWallpaperService.aidl \
core/java/android/service/chooser/IChooserTargetService.aidl \
core/java/android/service/chooser/IChooserTargetResult.aidl \
+ core/java/android/service/resolver/IResolverRankerService.aidl \
+ core/java/android/service/resolver/IResolverRankerResult.aidl \
core/java/android/text/ITextClassificationService.aidl \
core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl\
core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl\
@@ -729,6 +731,7 @@
frameworks/base/core/java/android/service/notification/SnoozeCriterion.aidl \
frameworks/base/core/java/android/service/notification/StatusBarNotification.aidl \
frameworks/base/core/java/android/service/chooser/ChooserTarget.aidl \
+ frameworks/base/core/java/android/service/resolver/ResolverTarget.aidl \
frameworks/base/core/java/android/speech/tts/Voice.aidl \
frameworks/base/core/java/android/app/usage/CacheQuotaHint.aidl \
frameworks/base/core/java/android/app/usage/ExternalStorageStats.aidl \
diff --git a/api/current.txt b/api/current.txt
index fe35f61..a8e5da8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12856,7 +12856,7 @@
public static class ColorSpace.Connector {
method public android.graphics.ColorSpace getDestination();
- method public android.graphics.ColorSpace.RenderIntent getIntent();
+ method public android.graphics.ColorSpace.RenderIntent getRenderIntent();
method public android.graphics.ColorSpace getSource();
method public float[] transform(float, float, float);
method public float[] transform(float[]);
@@ -24552,6 +24552,7 @@
method public static boolean isChannelUriForPassthroughInput(android.net.Uri);
method public static boolean isChannelUriForTunerInput(android.net.Uri);
method public static boolean isProgramUri(android.net.Uri);
+ field public static final java.lang.String ACTION_INITIALIZE_PROGRAMS = "android.media.tv.action.INITIALIZE_PROGRAMS";
field public static final java.lang.String ACTION_MAKE_CHANNEL_BROWSABLE = "android.media.tv.action.MAKE_CHANNEL_BROWSABLE";
field public static final java.lang.String ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT = "android.media.tv.action.PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT";
field public static final java.lang.String ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED = "android.media.tv.action.PREVIEW_PROGRAM_BROWSABLE_DISABLED";
@@ -37021,7 +37022,7 @@
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onDisconnected();
- method public void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback);
+ method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback);
method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillService";
field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
diff --git a/api/system-current.txt b/api/system-current.txt
index 5fff116..6112b54 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -53,6 +53,7 @@
field public static final java.lang.String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
field public static final java.lang.String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE";
field public static final java.lang.String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
+ field public static final java.lang.String BIND_RESOLVER_RANKER_SERVICE = "android.permission.BIND_RESOLVER_RANKER_SERVICE";
field public static final java.lang.String BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE";
field public static final java.lang.String BIND_SCREENING_SERVICE = "android.permission.BIND_SCREENING_SERVICE";
field public static final java.lang.String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE";
@@ -13618,7 +13619,7 @@
public static class ColorSpace.Connector {
method public android.graphics.ColorSpace getDestination();
- method public android.graphics.ColorSpace.RenderIntent getIntent();
+ method public android.graphics.ColorSpace.RenderIntent getRenderIntent();
method public android.graphics.ColorSpace getSource();
method public float[] transform(float, float, float);
method public float[] transform(float[]);
@@ -26521,6 +26522,7 @@
method public static boolean isChannelUriForPassthroughInput(android.net.Uri);
method public static boolean isChannelUriForTunerInput(android.net.Uri);
method public static boolean isProgramUri(android.net.Uri);
+ field public static final java.lang.String ACTION_INITIALIZE_PROGRAMS = "android.media.tv.action.INITIALIZE_PROGRAMS";
field public static final java.lang.String ACTION_MAKE_CHANNEL_BROWSABLE = "android.media.tv.action.MAKE_CHANNEL_BROWSABLE";
field public static final java.lang.String ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT = "android.media.tv.action.PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT";
field public static final java.lang.String ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED = "android.media.tv.action.PREVIEW_PROGRAM_BROWSABLE_DISABLED";
@@ -40091,7 +40093,7 @@
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onDisconnected();
- method public void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback);
+ method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback);
method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillService";
field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
@@ -40627,6 +40629,36 @@
}
+package android.service.resolver {
+
+ public abstract class ResolverRankerService extends android.app.Service {
+ ctor public ResolverRankerService();
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public void onPredictSharingProbabilities(java.util.List<android.service.resolver.ResolverTarget>);
+ method public void onTrainRankingModel(java.util.List<android.service.resolver.ResolverTarget>, int);
+ field public static final java.lang.String BIND_PERMISSION = "android.permission.BIND_RESOLVER_RANKER_SERVICE";
+ field public static final java.lang.String SERVICE_INTERFACE = "android.service.resolver.ResolverRankerService";
+ }
+
+ public final class ResolverTarget implements android.os.Parcelable {
+ ctor public ResolverTarget();
+ method public int describeContents();
+ method public float getChooserScore();
+ method public float getLaunchScore();
+ method public float getRecencyScore();
+ method public float getSelectProbability();
+ method public float getTimeSpentScore();
+ method public void setChooserScore(float);
+ method public void setLaunchScore(float);
+ method public void setRecencyScore(float);
+ method public void setSelectProbability(float);
+ method public void setTimeSpentScore(float);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.resolver.ResolverTarget> CREATOR;
+ }
+
+}
+
package android.service.restrictions {
public abstract class RestrictionsReceiver extends android.content.BroadcastReceiver {
diff --git a/api/test-current.txt b/api/test-current.txt
index 38897c8..82d5080 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -12906,7 +12906,7 @@
public static class ColorSpace.Connector {
method public android.graphics.ColorSpace getDestination();
- method public android.graphics.ColorSpace.RenderIntent getIntent();
+ method public android.graphics.ColorSpace.RenderIntent getRenderIntent();
method public android.graphics.ColorSpace getSource();
method public float[] transform(float, float, float);
method public float[] transform(float[]);
@@ -24665,6 +24665,7 @@
method public static boolean isChannelUriForPassthroughInput(android.net.Uri);
method public static boolean isChannelUriForTunerInput(android.net.Uri);
method public static boolean isProgramUri(android.net.Uri);
+ field public static final java.lang.String ACTION_INITIALIZE_PROGRAMS = "android.media.tv.action.INITIALIZE_PROGRAMS";
field public static final java.lang.String ACTION_MAKE_CHANNEL_BROWSABLE = "android.media.tv.action.MAKE_CHANNEL_BROWSABLE";
field public static final java.lang.String ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT = "android.media.tv.action.PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT";
field public static final java.lang.String ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED = "android.media.tv.action.PREVIEW_PROGRAM_BROWSABLE_DISABLED";
@@ -37179,7 +37180,7 @@
method public final android.os.IBinder onBind(android.content.Intent);
method public void onConnected();
method public void onDisconnected();
- method public void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback);
+ method public abstract void onFillRequest(android.app.assist.AssistStructure, android.os.Bundle, int, android.os.CancellationSignal, android.service.autofill.FillCallback);
method public abstract void onSaveRequest(android.app.assist.AssistStructure, android.os.Bundle, android.service.autofill.SaveCallback);
field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.AutofillService";
field public static final java.lang.String SERVICE_META_DATA = "android.autofill";
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 3137658..7a709ed 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -660,7 +660,7 @@
* @hide
* @param tid The identifier of the thread to change.
* @param group The target group for this thread from THREAD_GROUP_*.
- *
+ *
* @throws IllegalArgumentException Throws IllegalArgumentException if
* <var>tid</var> does not exist.
* @throws SecurityException Throws SecurityException if your process does
@@ -676,6 +676,21 @@
throws IllegalArgumentException, SecurityException;
/**
+ * Sets the scheduling group and the corresponding cpuset group
+ * @hide
+ * @param tid The identifier of the thread to change.
+ * @param group The target group for this thread from THREAD_GROUP_*.
+ *
+ * @throws IllegalArgumentException Throws IllegalArgumentException if
+ * <var>tid</var> does not exist.
+ * @throws SecurityException Throws SecurityException if your process does
+ * not have permission to modify the given thread, or to use the given
+ * priority.
+ */
+ public static final native void setThreadGroupAndCpuset(int tid, int group)
+ throws IllegalArgumentException, SecurityException;
+
+ /**
* Sets the scheduling group for a process and all child threads
* @hide
* @param pid The identifier of the process to change.
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index ef14095..aae22c1 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -216,24 +216,9 @@
* handling this fill request in order to save resources.
* @param callback object used to notify the result of the request.
*/
- public void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle data, int flags,
- @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback) {
- //TODO(b/33197203): make non-abstract once older method is removed
- onFillRequest(structure, data, cancellationSignal, callback);
- }
-
- /**
- * @hide
- * @deprecated - use {@link #onFillRequest(AssistStructure, Bundle, int,
- * CancellationSignal, FillCallback)} instead
- */
- //TODO(b/33197203): remove once clients are not using anymore
- @Deprecated
- public void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle data,
- @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback) {
- // Should never be called because it was abstract before.
- throw new UnsupportedOperationException("deprecated");
- }
+ public abstract void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle data,
+ int flags, @NonNull CancellationSignal cancellationSignal,
+ @NonNull FillCallback callback);
/**
* Called when user requests service to save the fields of an {@link Activity}.
diff --git a/core/java/android/service/resolver/IResolverRankerResult.aidl b/core/java/android/service/resolver/IResolverRankerResult.aidl
new file mode 100644
index 0000000..bda3154
--- /dev/null
+++ b/core/java/android/service/resolver/IResolverRankerResult.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.resolver;
+
+import android.service.resolver.ResolverTarget;
+
+/**
+ * @hide
+ */
+oneway interface IResolverRankerResult
+{
+ void sendResult(in List<ResolverTarget> results);
+}
\ No newline at end of file
diff --git a/core/java/android/service/resolver/IResolverRankerService.aidl b/core/java/android/service/resolver/IResolverRankerService.aidl
new file mode 100644
index 0000000..f0d747d
--- /dev/null
+++ b/core/java/android/service/resolver/IResolverRankerService.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.resolver;
+
+import android.service.resolver.IResolverRankerResult;
+import android.service.resolver.ResolverTarget;
+
+/**
+ * @hide
+ */
+oneway interface IResolverRankerService
+{
+ void predict(in List<ResolverTarget> targets, IResolverRankerResult result);
+ void train(in List<ResolverTarget> targets, int selectedPosition);
+}
\ No newline at end of file
diff --git a/core/java/android/service/resolver/ResolverRankerService.java b/core/java/android/service/resolver/ResolverRankerService.java
new file mode 100644
index 0000000..0506747
--- /dev/null
+++ b/core/java/android/service/resolver/ResolverRankerService.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.resolver;
+
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.RemoteException;
+import android.service.resolver.ResolverTarget;
+import android.util.Log;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A service to rank apps according to usage stats of apps, when the system is resolving targets for
+ * an Intent.
+ *
+ * <p>To extend this class, you must declare the service in your manifest file with the
+ * {@link android.Manifest.permission#BIND_RESOLVER_RANKER_SERVICE} permission, and include an
+ * intent filter with the {@link #SERVICE_INTERFACE} action. For example:</p>
+ * <pre>
+ * <service android:name=".MyResolverRankerService"
+ * android:exported="true"
+ * android:priority="100"
+ * android:permission="android.permission.BIND_RESOLVER_RANKER_SERVICE">
+ * <intent-filter>
+ * <action android:name="android.service.resolver.ResolverRankerService" />
+ * </intent-filter>
+ * </service>
+ * </pre>
+ * @hide
+ */
+@SystemApi
+public abstract class ResolverRankerService extends Service {
+
+ private static final String TAG = "ResolverRankerService";
+
+ private static final boolean DEBUG = false;
+
+ /**
+ * The Intent action that a service must respond to. Add it to the intent filter of the service
+ * in its manifest.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE = "android.service.resolver.ResolverRankerService";
+
+ /**
+ * The permission that a service must require to ensure that only Android system can bind to it.
+ * If this permission is not enforced in the AndroidManifest of the service, the system will
+ * skip that service.
+ */
+ public static final String BIND_PERMISSION = "android.permission.BIND_RESOLVER_RANKER_SERVICE";
+
+ private ResolverRankerServiceWrapper mWrapper = null;
+
+ /**
+ * Called by the system to retrieve a list of probabilities to rank apps/options. To implement
+ * it, set selectProbability of each input {@link ResolverTarget}. The higher the
+ * selectProbability is, the more likely the {@link ResolverTarget} will be selected by the
+ * user. Override this function to provide prediction results.
+ *
+ * @param targets a list of {@link ResolverTarget}, for the list of apps to be ranked.
+ *
+ * @throws Exception when the prediction task fails.
+ */
+ public void onPredictSharingProbabilities(final List<ResolverTarget> targets) {}
+
+ /**
+ * Called by the system to train/update a ranking service, after the user makes a selection from
+ * the ranked list of apps. Override this function to enable model updates.
+ *
+ * @param targets a list of {@link ResolverTarget}, for the list of apps to be ranked.
+ * @param selectedPosition the position of the selected app in the list.
+ *
+ * @throws Exception when the training task fails.
+ */
+ public void onTrainRankingModel(
+ final List<ResolverTarget> targets, final int selectedPosition) {}
+
+ private static final String HANDLER_THREAD_NAME = "RESOLVER_RANKER_SERVICE";
+ private volatile Handler mHandler;
+ private HandlerThread mHandlerThread;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (DEBUG) Log.d(TAG, "onBind " + intent);
+ if (!SERVICE_INTERFACE.equals(intent.getAction())) {
+ if (DEBUG) Log.d(TAG, "bad intent action " + intent.getAction() + "; returning null");
+ return null;
+ }
+ if (mHandlerThread == null) {
+ mHandlerThread = new HandlerThread(HANDLER_THREAD_NAME);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ }
+ if (mWrapper == null) {
+ mWrapper = new ResolverRankerServiceWrapper();
+ }
+ return mWrapper;
+ }
+
+ @Override
+ public void onDestroy() {
+ mHandler = null;
+ if (mHandlerThread != null) {
+ mHandlerThread.quitSafely();
+ }
+ super.onDestroy();
+ }
+
+ private static void sendResult(List<ResolverTarget> targets, IResolverRankerResult result) {
+ try {
+ result.sendResult(targets);
+ } catch (Exception e) {
+ Log.e(TAG, "failed to send results: " + e);
+ }
+ }
+
+ private class ResolverRankerServiceWrapper extends IResolverRankerService.Stub {
+
+ @Override
+ public void predict(final List<ResolverTarget> targets, final IResolverRankerResult result)
+ throws RemoteException {
+ Runnable predictRunnable = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "predict calls onPredictSharingProbabilities.");
+ }
+ onPredictSharingProbabilities(targets);
+ sendResult(targets, result);
+ } catch (Exception e) {
+ Log.e(TAG, "onPredictSharingProbabilities failed; send null results: " + e);
+ sendResult(null, result);
+ }
+ }
+ };
+ final Handler h = mHandler;
+ if (h != null) {
+ h.post(predictRunnable);
+ }
+ }
+
+ @Override
+ public void train(final List<ResolverTarget> targets, final int selectedPosition)
+ throws RemoteException {
+ Runnable trainRunnable = new Runnable() {
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "train calls onTranRankingModel");
+ }
+ onTrainRankingModel(targets, selectedPosition);
+ } catch (Exception e) {
+ Log.e(TAG, "onTrainRankingModel failed; skip train: " + e);
+ }
+ }
+ };
+ final Handler h = mHandler;
+ if (h != null) {
+ h.post(trainRunnable);
+ }
+ }
+ }
+}
diff --git a/core/java/android/service/resolver/ResolverTarget.aidl b/core/java/android/service/resolver/ResolverTarget.aidl
new file mode 100644
index 0000000..6cab2d4
--- /dev/null
+++ b/core/java/android/service/resolver/ResolverTarget.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.resolver;
+
+/**
+ * @hide
+ */
+parcelable ResolverTarget;
diff --git a/core/java/android/service/resolver/ResolverTarget.java b/core/java/android/service/resolver/ResolverTarget.java
new file mode 100644
index 0000000..fb3e2d7
--- /dev/null
+++ b/core/java/android/service/resolver/ResolverTarget.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.resolver;
+
+import android.annotation.SystemApi;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+
+import java.util.Map;
+
+/**
+ * A ResolverTarget contains features by which an app or option will be ranked, in
+ * {@link ResolverRankerService}.
+ * @hide
+ */
+@SystemApi
+public final class ResolverTarget implements Parcelable {
+ private static final String TAG = "ResolverTarget";
+
+ /**
+ * a float score for recency of last use.
+ */
+ private float mRecencyScore;
+
+ /**
+ * a float score for total time spent.
+ */
+ private float mTimeSpentScore;
+
+ /**
+ * a float score for number of launches.
+ */
+ private float mLaunchScore;
+
+ /**
+ * a float score for number of selected.
+ */
+ private float mChooserScore;
+
+ /**
+ * a float score for the probability to be selected.
+ */
+ private float mSelectProbability;
+
+ // constructor for the class.
+ public ResolverTarget() {}
+
+ ResolverTarget(Parcel in) {
+ mRecencyScore = in.readFloat();
+ mTimeSpentScore = in.readFloat();
+ mLaunchScore = in.readFloat();
+ mChooserScore = in.readFloat();
+ mSelectProbability = in.readFloat();
+ }
+
+ /**
+ * Gets the score for how recently the target was used in the foreground.
+ *
+ * @return a float score whose range is [0, 1]. The higher the score is, the more recently the
+ * target was used.
+ */
+ public float getRecencyScore() {
+ return mRecencyScore;
+ }
+
+ /**
+ * Sets the score for how recently the target was used in the foreground.
+ *
+ * @param recencyScore a float score whose range is [0, 1]. The higher the score is, the more
+ * recently the target was used.
+ */
+ public void setRecencyScore(float recencyScore) {
+ this.mRecencyScore = recencyScore;
+ }
+
+ /**
+ * Gets the score for how long the target has been used in the foreground.
+ *
+ * @return a float score whose range is [0, 1]. The higher the score is, the longer the target
+ * has been used for.
+ */
+ public float getTimeSpentScore() {
+ return mTimeSpentScore;
+ }
+
+ /**
+ * Sets the score for how long the target has been used in the foreground.
+ *
+ * @param timeSpentScore a float score whose range is [0, 1]. The higher the score is, the
+ * longer the target has been used for.
+ */
+ public void setTimeSpentScore(float timeSpentScore) {
+ this.mTimeSpentScore = timeSpentScore;
+ }
+
+ /**
+ * Gets the score for how many times the target has been launched to the foreground.
+ *
+ * @return a float score whose range is [0, 1]. The higher the score is, the more times the
+ * target has been launched.
+ */
+ public float getLaunchScore() {
+ return mLaunchScore;
+ }
+
+ /**
+ * Sets the score for how many times the target has been launched to the foreground.
+ *
+ * @param launchScore a float score whose range is [0, 1]. The higher the score is, the more
+ * times the target has been launched.
+ */
+ public void setLaunchScore(float launchScore) {
+ this.mLaunchScore = launchScore;
+ }
+
+ /**
+ * Gets the score for how many times the target has been selected by the user to share the same
+ * types of content.
+ *
+ * @return a float score whose range is [0, 1]. The higher the score is, the
+ * more times the target has been selected by the user to share the same types of content for.
+ */
+ public float getChooserScore() {
+ return mChooserScore;
+ }
+
+ /**
+ * Sets the score for how many times the target has been selected by the user to share the same
+ * types of content.
+ *
+ * @param chooserScore a float score whose range is [0, 1]. The higher the score is, the more
+ * times the target has been selected by the user to share the same types
+ * of content for.
+ */
+ public void setChooserScore(float chooserScore) {
+ this.mChooserScore = chooserScore;
+ }
+
+ /**
+ * Gets the probability of how likely this target will be selected by the user.
+ *
+ * @return a float score whose range is [0, 1]. The higher the score is, the more likely the
+ * user is going to select this target.
+ */
+ public float getSelectProbability() {
+ return mSelectProbability;
+ }
+
+ /**
+ * Sets the probability for how like this target will be selected by the user.
+ *
+ * @param selectProbability a float score whose range is [0, 1]. The higher the score is, the
+ * more likely tht user is going to select this target.
+ */
+ public void setSelectProbability(float selectProbability) {
+ this.mSelectProbability = selectProbability;
+ }
+
+ // serialize the class to a string.
+ @Override
+ public String toString() {
+ return "ResolverTarget{"
+ + mRecencyScore + ", "
+ + mTimeSpentScore + ", "
+ + mLaunchScore + ", "
+ + mChooserScore + ", "
+ + mSelectProbability + "}";
+ }
+
+ // describes the kinds of special objects contained in this Parcelable instance's marshaled
+ // representation.
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ // flattens this object in to a Parcel.
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeFloat(mRecencyScore);
+ dest.writeFloat(mTimeSpentScore);
+ dest.writeFloat(mLaunchScore);
+ dest.writeFloat(mChooserScore);
+ dest.writeFloat(mSelectProbability);
+ }
+
+ // creator definition for the class.
+ public static final Creator<ResolverTarget> CREATOR
+ = new Creator<ResolverTarget>() {
+ @Override
+ public ResolverTarget createFromParcel(Parcel source) {
+ return new ResolverTarget(source);
+ }
+
+ @Override
+ public ResolverTarget[] newArray(int size) {
+ return new ResolverTarget[size];
+ }
+ };
+}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 7d9253b..58da92f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -57,6 +57,7 @@
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.fonts.FontVariationAxis;
+import android.icu.text.DecimalFormatSymbols;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.LocaleList;
@@ -11058,6 +11059,26 @@
return TextDirectionHeuristics.LTR;
}
+ if (mEditor != null
+ && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
+ == EditorInfo.TYPE_CLASS_PHONE) {
+ // Phone numbers must be in the direction of the locale's digits. Most locales have LTR
+ // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have
+ // RTL digits.
+ final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale());
+ final String zero = symbols.getDigitStrings()[0];
+ // In case the zero digit is multi-codepoint, just use the first codepoint to determine
+ // direction.
+ final int firstCodepoint = zero.codePointAt(0);
+ final byte digitDirection = Character.getDirectionality(firstCodepoint);
+ if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT
+ || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) {
+ return TextDirectionHeuristics.RTL;
+ } else {
+ return TextDirectionHeuristics.LTR;
+ }
+ }
+
// Always need to resolve layout direction first
final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
diff --git a/core/java/com/android/internal/app/LRResolverRankerService.java b/core/java/com/android/internal/app/LRResolverRankerService.java
new file mode 100644
index 0000000..1cad7c7
--- /dev/null
+++ b/core/java/com/android/internal/app/LRResolverRankerService.java
@@ -0,0 +1,199 @@
+/*
+ * 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.internal.app;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.storage.StorageManager;
+import android.service.resolver.ResolverRankerService;
+import android.service.resolver.ResolverTarget;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A Logistic Regression based {@link android.service.resolver.ResolverRankerService}, to be used
+ * in {@link ResolverComparator}.
+ */
+public final class LRResolverRankerService extends ResolverRankerService {
+ private static final String TAG = "LRResolverRankerService";
+
+ private static final boolean DEBUG = false;
+
+ private static final String PARAM_SHARED_PREF_NAME = "resolver_ranker_params";
+ private static final String BIAS_PREF_KEY = "bias";
+ private static final String VERSION_PREF_KEY = "version";
+
+ private static final String LAUNCH_SCORE = "launch";
+ private static final String TIME_SPENT_SCORE = "timeSpent";
+ private static final String RECENCY_SCORE = "recency";
+ private static final String CHOOSER_SCORE = "chooser";
+
+ // parameters for a pre-trained model, to initialize the app ranker. When updating the
+ // pre-trained model, please update these params, as well as initModel().
+ private static final int CURRENT_VERSION = 1;
+ private static final float LEARNING_RATE = 0.0001f;
+ private static final float REGULARIZER_PARAM = 0.0001f;
+
+ private SharedPreferences mParamSharedPref;
+ private ArrayMap<String, Float> mFeatureWeights;
+ private float mBias;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ initModel();
+ return super.onBind(intent);
+ }
+
+ @Override
+ public void onPredictSharingProbabilities(List<ResolverTarget> targets) {
+ final int size = targets.size();
+ for (int i = 0; i < size; ++i) {
+ ResolverTarget target = targets.get(i);
+ ArrayMap<String, Float> features = getFeatures(target);
+ target.setSelectProbability(predict(features));
+ }
+ }
+
+ @Override
+ public void onTrainRankingModel(List<ResolverTarget> targets, int selectedPosition) {
+ final int size = targets.size();
+ if (selectedPosition < 0 || selectedPosition >= size) {
+ if (DEBUG) {
+ Log.d(TAG, "Invalid Position of Selected App " + selectedPosition);
+ }
+ return;
+ }
+ final ArrayMap<String, Float> positive = getFeatures(targets.get(selectedPosition));
+ final float positiveProbability = targets.get(selectedPosition).getSelectProbability();
+ final int targetSize = targets.size();
+ for (int i = 0; i < targetSize; ++i) {
+ if (i == selectedPosition) {
+ continue;
+ }
+ final ArrayMap<String, Float> negative = getFeatures(targets.get(i));
+ final float negativeProbability = targets.get(i).getSelectProbability();
+ if (negativeProbability > positiveProbability) {
+ update(negative, negativeProbability, false);
+ update(positive, positiveProbability, true);
+ }
+ }
+ commitUpdate();
+ }
+
+ private void initModel() {
+ mParamSharedPref = getParamSharedPref();
+ mFeatureWeights = new ArrayMap<>(4);
+ if (mParamSharedPref == null ||
+ mParamSharedPref.getInt(VERSION_PREF_KEY, 0) < CURRENT_VERSION) {
+ // Initializing the app ranker to a pre-trained model. When updating the pre-trained
+ // model, please increment CURRENT_VERSION, and update LEARNING_RATE and
+ // REGULARIZER_PARAM.
+ mBias = -1.6568f;
+ mFeatureWeights.put(LAUNCH_SCORE, 2.5543f);
+ mFeatureWeights.put(TIME_SPENT_SCORE, 2.8412f);
+ mFeatureWeights.put(RECENCY_SCORE, 0.269f);
+ mFeatureWeights.put(CHOOSER_SCORE, 4.2222f);
+ } else {
+ mBias = mParamSharedPref.getFloat(BIAS_PREF_KEY, 0.0f);
+ mFeatureWeights.put(LAUNCH_SCORE, mParamSharedPref.getFloat(LAUNCH_SCORE, 0.0f));
+ mFeatureWeights.put(
+ TIME_SPENT_SCORE, mParamSharedPref.getFloat(TIME_SPENT_SCORE, 0.0f));
+ mFeatureWeights.put(RECENCY_SCORE, mParamSharedPref.getFloat(RECENCY_SCORE, 0.0f));
+ mFeatureWeights.put(CHOOSER_SCORE, mParamSharedPref.getFloat(CHOOSER_SCORE, 0.0f));
+ }
+ }
+
+ private ArrayMap<String, Float> getFeatures(ResolverTarget target) {
+ ArrayMap<String, Float> features = new ArrayMap<>(4);
+ features.put(RECENCY_SCORE, target.getRecencyScore());
+ features.put(TIME_SPENT_SCORE, target.getTimeSpentScore());
+ features.put(LAUNCH_SCORE, target.getLaunchScore());
+ features.put(CHOOSER_SCORE, target.getChooserScore());
+ return features;
+ }
+
+ private float predict(ArrayMap<String, Float> target) {
+ if (target == null) {
+ return 0.0f;
+ }
+ final int featureSize = target.size();
+ float sum = 0.0f;
+ for (int i = 0; i < featureSize; i++) {
+ String featureName = target.keyAt(i);
+ float weight = mFeatureWeights.getOrDefault(featureName, 0.0f);
+ sum += weight * target.valueAt(i);
+ }
+ return (float) (1.0 / (1.0 + Math.exp(-mBias - sum)));
+ }
+
+ private void update(ArrayMap<String, Float> target, float predict, boolean isSelected) {
+ if (target == null) {
+ return;
+ }
+ final int featureSize = target.size();
+ float error = isSelected ? 1.0f - predict : -predict;
+ for (int i = 0; i < featureSize; i++) {
+ String featureName = target.keyAt(i);
+ float currentWeight = mFeatureWeights.getOrDefault(featureName, 0.0f);
+ mBias += LEARNING_RATE * error;
+ currentWeight = currentWeight - LEARNING_RATE * REGULARIZER_PARAM * currentWeight +
+ LEARNING_RATE * error * target.valueAt(i);
+ mFeatureWeights.put(featureName, currentWeight);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Weights: " + mFeatureWeights + " Bias: " + mBias);
+ }
+ }
+
+ private void commitUpdate() {
+ try {
+ SharedPreferences.Editor editor = mParamSharedPref.edit();
+ editor.putFloat(BIAS_PREF_KEY, mBias);
+ final int size = mFeatureWeights.size();
+ for (int i = 0; i < size; i++) {
+ editor.putFloat(mFeatureWeights.keyAt(i), mFeatureWeights.valueAt(i));
+ }
+ editor.putInt(VERSION_PREF_KEY, CURRENT_VERSION);
+ editor.apply();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to commit update" + e);
+ }
+ }
+
+ private SharedPreferences getParamSharedPref() {
+ // The package info in the context isn't initialized in the way it is for normal apps,
+ // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
+ // build the path manually below using the same policy that appears in ContextImpl.
+ if (DEBUG) {
+ Log.d(TAG, "Context Package Name: " + getPackageName());
+ }
+ final File prefsFile = new File(new File(
+ Environment.getDataUserCePackageDirectory(
+ StorageManager.UUID_PRIVATE_INTERNAL, getUserId(), getPackageName()),
+ "shared_prefs"),
+ PARAM_SHARED_PREF_NAME + ".xml");
+ return getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
+ }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 3f1c9ad..622b708 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -530,6 +530,9 @@
getMainThreadHandler().removeCallbacks(mPostListReadyRunnable);
mPostListReadyRunnable = null;
}
+ if (mAdapter != null && mAdapter.mResolverListController != null) {
+ mAdapter.mResolverListController.destroy();
+ }
}
@Override
diff --git a/core/java/com/android/internal/app/ResolverComparator.java b/core/java/com/android/internal/app/ResolverComparator.java
index 096fcb8..73b62a5 100644
--- a/core/java/com/android/internal/app/ResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverComparator.java
@@ -26,20 +26,34 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.SharedPreferences;
+import android.content.ServiceConnection;
import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
import android.os.storage.StorageManager;
import android.os.UserHandle;
+import android.service.resolver.IResolverRankerService;
+import android.service.resolver.IResolverRankerResult;
+import android.service.resolver.ResolverRankerService;
+import android.service.resolver.ResolverTarget;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import java.io.File;
+import java.lang.InterruptedException;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Comparator;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -61,11 +75,15 @@
private static final float RECENCY_MULTIPLIER = 2.f;
- // feature names used in ranking.
- private static final String LAUNCH_SCORE = "launch";
- private static final String TIME_SPENT_SCORE = "timeSpent";
- private static final String RECENCY_SCORE = "recency";
- private static final String CHOOSER_SCORE = "chooser";
+ // message types
+ private static final int RESOLVER_RANKER_SERVICE_RESULT = 0;
+ private static final int RESOLVER_RANKER_RESULT_TIMEOUT = 1;
+
+ // timeout for establishing connections with a ResolverRankerService.
+ private static final int CONNECTION_COST_TIMEOUT_MILLIS = 200;
+ // timeout for establishing connections with a ResolverRankerService, collecting features and
+ // predicting ranking scores.
+ private static final int WATCHDOG_TIMEOUT_MILLIS = 500;
private final Collator mCollator;
private final boolean mHttp;
@@ -74,18 +92,74 @@
private final Map<String, UsageStats> mStats;
private final long mCurrentTime;
private final long mSinceTime;
- private final LinkedHashMap<ComponentName, ScoredTarget> mScoredTargets = new LinkedHashMap<>();
+ private final LinkedHashMap<ComponentName, ResolverTarget> mTargetsDict = new LinkedHashMap<>();
private final String mReferrerPackage;
+ private final Object mLock = new Object();
+ private ArrayList<ResolverTarget> mTargets;
private String mContentType;
private String[] mAnnotations;
private String mAction;
- private LogisticRegressionAppRanker mRanker;
+ private IResolverRankerService mRanker;
+ private ResolverRankerServiceConnection mConnection;
+ private AfterCompute mAfterCompute;
+ private Context mContext;
+ private CountDownLatch mConnectSignal;
- public ResolverComparator(Context context, Intent intent, String referrerPackage) {
+ private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case RESOLVER_RANKER_SERVICE_RESULT:
+ if (DEBUG) {
+ Log.d(TAG, "RESOLVER_RANKER_SERVICE_RESULT");
+ }
+ if (mHandler.hasMessages(RESOLVER_RANKER_RESULT_TIMEOUT)) {
+ if (msg.obj != null) {
+ final List<ResolverTarget> receivedTargets =
+ (List<ResolverTarget>) msg.obj;
+ if (receivedTargets != null && mTargets != null
+ && receivedTargets.size() == mTargets.size()) {
+ final int size = mTargets.size();
+ for (int i = 0; i < size; ++i) {
+ mTargets.get(i).setSelectProbability(
+ receivedTargets.get(i).getSelectProbability());
+ }
+ } else {
+ Log.e(TAG, "Sizes of sent and received ResolverTargets diff.");
+ }
+ } else {
+ Log.e(TAG, "Receiving null prediction results.");
+ }
+ mHandler.removeMessages(RESOLVER_RANKER_RESULT_TIMEOUT);
+ mAfterCompute.afterCompute();
+ }
+ break;
+
+ case RESOLVER_RANKER_RESULT_TIMEOUT:
+ if (DEBUG) {
+ Log.d(TAG, "RESOLVER_RANKER_RESULT_TIMEOUT; unbinding services");
+ }
+ mHandler.removeMessages(RESOLVER_RANKER_SERVICE_RESULT);
+ mAfterCompute.afterCompute();
+ break;
+
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ };
+
+ public interface AfterCompute {
+ public void afterCompute ();
+ }
+
+ public ResolverComparator(Context context, Intent intent, String referrerPackage,
+ AfterCompute afterCompute) {
mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
String scheme = intent.getScheme();
mHttp = "http".equals(scheme) || "https".equals(scheme);
mReferrerPackage = referrerPackage;
+ mAfterCompute = afterCompute;
+ mContext = context;
mPm = context.getPackageManager();
mUsm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
@@ -96,9 +170,9 @@
mContentType = intent.getType();
getContentAnnotations(intent);
mAction = intent.getAction();
- mRanker = new LogisticRegressionAppRanker(context);
}
+ // get annotations of content from intent.
public void getContentAnnotations(Intent intent) {
ArrayList<String> annotations = intent.getStringArrayListExtra(
Intent.EXTRA_CONTENT_ANNOTATIONS);
@@ -114,20 +188,24 @@
}
}
+ public void setCallBack(AfterCompute afterCompute) {
+ mAfterCompute = afterCompute;
+ }
+
+ // compute features for each target according to usage stats of targets.
public void compute(List<ResolvedComponentInfo> targets) {
- mScoredTargets.clear();
+ reset();
final long recentSinceTime = mCurrentTime - RECENCY_TIME_PERIOD;
- long mostRecentlyUsedTime = recentSinceTime + 1;
- long mostTimeSpent = 1;
- int mostLaunched = 1;
- int mostSelected = 1;
+ float mostRecencyScore = 1.0f;
+ float mostTimeSpentScore = 1.0f;
+ float mostLaunchScore = 1.0f;
+ float mostChooserScore = 1.0f;
for (ResolvedComponentInfo target : targets) {
- final ScoredTarget scoredTarget
- = new ScoredTarget(target.getResolveInfoAt(0).activityInfo);
- mScoredTargets.put(target.name, scoredTarget);
+ final ResolverTarget resolverTarget = new ResolverTarget();
+ mTargetsDict.put(target.name, resolverTarget);
final UsageStats pkStats = mStats.get(target.name.getPackageName());
if (pkStats != null) {
// Only count recency for apps that weren't the caller
@@ -135,31 +213,33 @@
// Persistent processes muck this up, so omit them too.
if (!target.name.getPackageName().equals(mReferrerPackage)
&& !isPersistentProcess(target)) {
- final long lastTimeUsed = pkStats.getLastTimeUsed();
- scoredTarget.lastTimeUsed = lastTimeUsed;
- if (lastTimeUsed > mostRecentlyUsedTime) {
- mostRecentlyUsedTime = lastTimeUsed;
+ final float recencyScore =
+ (float) Math.max(pkStats.getLastTimeUsed() - recentSinceTime, 0);
+ resolverTarget.setRecencyScore(recencyScore);
+ if (recencyScore > mostRecencyScore) {
+ mostRecencyScore = recencyScore;
}
}
- final long timeSpent = pkStats.getTotalTimeInForeground();
- scoredTarget.timeSpent = timeSpent;
- if (timeSpent > mostTimeSpent) {
- mostTimeSpent = timeSpent;
+ final float timeSpentScore = (float) pkStats.getTotalTimeInForeground();
+ resolverTarget.setTimeSpentScore(timeSpentScore);
+ if (timeSpentScore > mostTimeSpentScore) {
+ mostTimeSpentScore = timeSpentScore;
}
- final int launched = pkStats.mLaunchCount;
- scoredTarget.launchCount = launched;
- if (launched > mostLaunched) {
- mostLaunched = launched;
+ final float launchScore = (float) pkStats.mLaunchCount;
+ resolverTarget.setLaunchScore(launchScore);
+ if (launchScore > mostLaunchScore) {
+ mostLaunchScore = launchScore;
}
- int selected = 0;
+ float chooserScore = 0.0f;
if (pkStats.mChooserCounts != null && mAction != null
&& pkStats.mChooserCounts.get(mAction) != null) {
- selected = pkStats.mChooserCounts.get(mAction).getOrDefault(mContentType, 0);
+ chooserScore = (float) pkStats.mChooserCounts.get(mAction)
+ .getOrDefault(mContentType, 0);
if (mAnnotations != null) {
final int size = mAnnotations.length;
for (int i = 0; i < size; i++) {
- selected += pkStats.mChooserCounts.get(mAction)
+ chooserScore += (float) pkStats.mChooserCounts.get(mAction)
.getOrDefault(mAnnotations[i], 0);
}
}
@@ -169,44 +249,37 @@
Log.d(TAG, "Action type is null");
} else {
Log.d(TAG, "Chooser Count of " + mAction + ":" +
- target.name.getPackageName() + " is " + Integer.toString(selected));
+ target.name.getPackageName() + " is " +
+ Float.toString(chooserScore));
}
}
- scoredTarget.chooserCount = selected;
- if (selected > mostSelected) {
- mostSelected = selected;
+ resolverTarget.setChooserScore(chooserScore);
+ if (chooserScore > mostChooserScore) {
+ mostChooserScore = chooserScore;
}
}
}
-
if (DEBUG) {
- Log.d(TAG, "compute - mostRecentlyUsedTime: " + mostRecentlyUsedTime
- + " mostTimeSpent: " + mostTimeSpent
- + " recentSinceTime: " + recentSinceTime
- + " mostLaunched: " + mostLaunched);
+ Log.d(TAG, "compute - mostRecencyScore: " + mostRecencyScore
+ + " mostTimeSpentScore: " + mostTimeSpentScore
+ + " mostLaunchScore: " + mostLaunchScore
+ + " mostChooserScore: " + mostChooserScore);
}
- for (ScoredTarget target : mScoredTargets.values()) {
- final float recency = (float) Math.max(target.lastTimeUsed - recentSinceTime, 0)
- / (mostRecentlyUsedTime - recentSinceTime);
- target.setFeatures((float) target.launchCount / mostLaunched,
- (float) target.timeSpent / mostTimeSpent,
- recency * recency * RECENCY_MULTIPLIER,
- (float) target.chooserCount / mostSelected);
- target.selectProb = mRanker.predict(target.getFeatures());
+ mTargets = new ArrayList<>(mTargetsDict.values());
+ for (ResolverTarget target : mTargets) {
+ final float recency = target.getRecencyScore() / mostRecencyScore;
+ setFeatures(target, recency * recency * RECENCY_MULTIPLIER,
+ target.getLaunchScore() / mostLaunchScore,
+ target.getTimeSpentScore() / mostTimeSpentScore,
+ target.getChooserScore() / mostChooserScore);
+ addDefaultSelectProbability(target);
if (DEBUG) {
Log.d(TAG, "Scores: " + target);
}
}
- }
-
- static boolean isPersistentProcess(ResolvedComponentInfo rci) {
- if (rci != null && rci.getCount() > 0) {
- return (rci.getResolveInfoAt(0).activityInfo.applicationInfo.flags &
- ApplicationInfo.FLAG_PERSISTENT) != 0;
- }
- return false;
+ predictSelectProbabilities(mTargets);
}
@Override
@@ -245,16 +318,16 @@
// Pinned items stay stable within a normal lexical sort and ignore scoring.
if (!lPinned && !rPinned) {
if (mStats != null) {
- final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName(
+ final ResolverTarget lhsTarget = mTargetsDict.get(new ComponentName(
lhs.activityInfo.packageName, lhs.activityInfo.name));
- final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName(
+ final ResolverTarget rhsTarget = mTargetsDict.get(new ComponentName(
rhs.activityInfo.packageName, rhs.activityInfo.name));
- final int selectProbDiff = Float.compare(
- rhsTarget.selectProb, lhsTarget.selectProb);
+ final int selectProbabilityDiff = Float.compare(
+ rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability());
- if (selectProbDiff != 0) {
- return selectProbDiff > 0 ? 1 : -1;
+ if (selectProbabilityDiff != 0) {
+ return selectProbabilityDiff > 0 ? 1 : -1;
}
}
}
@@ -268,177 +341,234 @@
}
public float getScore(ComponentName name) {
- final ScoredTarget target = mScoredTargets.get(name);
+ final ResolverTarget target = mTargetsDict.get(name);
if (target != null) {
- return target.selectProb;
+ return target.getSelectProbability();
}
return 0;
}
- static class ScoredTarget {
- public final ComponentInfo componentInfo;
- public long lastTimeUsed;
- public long timeSpent;
- public long launchCount;
- public long chooserCount;
- public ArrayMap<String, Float> features;
- public float selectProb;
-
- public ScoredTarget(ComponentInfo ci) {
- componentInfo = ci;
- features = new ArrayMap<>(5);
- }
-
- @Override
- public String toString() {
- return "ScoredTarget{" + componentInfo
- + " lastTimeUsed: " + lastTimeUsed
- + " timeSpent: " + timeSpent
- + " launchCount: " + launchCount
- + " chooserCount: " + chooserCount
- + " selectProb: " + selectProb
- + "}";
- }
-
- public void setFeatures(float launchCountScore, float usageTimeScore, float recencyScore,
- float chooserCountScore) {
- features.put(LAUNCH_SCORE, launchCountScore);
- features.put(TIME_SPENT_SCORE, usageTimeScore);
- features.put(RECENCY_SCORE, recencyScore);
- features.put(CHOOSER_SCORE, chooserCountScore);
- }
-
- public ArrayMap<String, Float> getFeatures() {
- return features;
- }
- }
-
public void updateChooserCounts(String packageName, int userId, String action) {
if (mUsm != null) {
mUsm.reportChooserSelection(packageName, userId, mContentType, mAnnotations, action);
}
}
+ // update ranking model when the connection to it is valid.
public void updateModel(ComponentName componentName) {
- if (mScoredTargets == null || componentName == null ||
- !mScoredTargets.containsKey(componentName)) {
- return;
- }
- ScoredTarget selected = mScoredTargets.get(componentName);
- for (ComponentName targetComponent : mScoredTargets.keySet()) {
- if (targetComponent.equals(componentName)) {
- continue;
- }
- ScoredTarget target = mScoredTargets.get(targetComponent);
- // A potential point of optimization. Save updates or derive a closed form for the
- // positive case, to avoid calculating them repeatedly.
- if (target.selectProb >= selected.selectProb) {
- mRanker.update(target.getFeatures(), target.selectProb, false);
- mRanker.update(selected.getFeatures(), selected.selectProb, true);
+ synchronized (mLock) {
+ if (mRanker != null) {
+ try {
+ int selectedPos = new ArrayList<ComponentName>(mTargetsDict.keySet())
+ .indexOf(componentName);
+ if (selectedPos > 0) {
+ mRanker.train(mTargets, selectedPos);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Selected a unknown component: " + componentName);
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in Train: " + e);
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Ranker is null; skip updateModel.");
+ }
}
}
- mRanker.commitUpdate();
}
- class LogisticRegressionAppRanker {
- private static final String PARAM_SHARED_PREF_NAME = "resolver_ranker_params";
- private static final String BIAS_PREF_KEY = "bias";
- private static final String VERSION_PREF_KEY = "version";
-
- // parameters for a pre-trained model, to initialize the app ranker. When updating the
- // pre-trained model, please update these params, as well as initModel().
- private static final int CURRENT_VERSION = 1;
- private static final float LEARNING_RATE = 0.0001f;
- private static final float REGULARIZER_PARAM = 0.0001f;
-
- private SharedPreferences mParamSharedPref;
- private ArrayMap<String, Float> mFeatureWeights;
- private float mBias;
-
- public LogisticRegressionAppRanker(Context context) {
- mParamSharedPref = getParamSharedPref(context);
- initModel();
+ // unbind the service and clear unhandled messges.
+ public void destroy() {
+ mHandler.removeMessages(RESOLVER_RANKER_SERVICE_RESULT);
+ mHandler.removeMessages(RESOLVER_RANKER_RESULT_TIMEOUT);
+ if (mConnection != null) {
+ mContext.unbindService(mConnection);
+ mConnection.destroy();
}
-
- public float predict(ArrayMap<String, Float> target) {
- if (target == null) {
- return 0.0f;
- }
- final int featureSize = target.size();
- float sum = 0.0f;
- for (int i = 0; i < featureSize; i++) {
- String featureName = target.keyAt(i);
- float weight = mFeatureWeights.getOrDefault(featureName, 0.0f);
- sum += weight * target.valueAt(i);
- }
- return (float) (1.0 / (1.0 + Math.exp(-mBias - sum)));
+ if (DEBUG) {
+ Log.d(TAG, "Unbinded Resolver Ranker.");
}
+ }
- public void update(ArrayMap<String, Float> target, float predict, boolean isSelected) {
- if (target == null) {
+ // connect to a ranking service.
+ private void initRanker(Context context) {
+ synchronized (mLock) {
+ if (mConnection != null && mRanker != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Ranker still exists; reusing the existing one.");
+ }
return;
}
- final int featureSize = target.size();
- float error = isSelected ? 1.0f - predict : -predict;
- for (int i = 0; i < featureSize; i++) {
- String featureName = target.keyAt(i);
- float currentWeight = mFeatureWeights.getOrDefault(featureName, 0.0f);
- mBias += LEARNING_RATE * error;
- currentWeight = currentWeight - LEARNING_RATE * REGULARIZER_PARAM * currentWeight +
- LEARNING_RATE * error * target.valueAt(i);
- mFeatureWeights.put(featureName, currentWeight);
+ }
+ Intent intent = resolveRankerService();
+ if (intent == null) {
+ return;
+ }
+ mConnectSignal = new CountDownLatch(1);
+ mConnection = new ResolverRankerServiceConnection(mConnectSignal);
+ context.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
+ }
+
+ // resolve the service for ranking.
+ private Intent resolveRankerService() {
+ Intent intent = new Intent(ResolverRankerService.SERVICE_INTERFACE);
+ final List<ResolveInfo> resolveInfos = mPm.queryIntentServices(intent, 0);
+ for (ResolveInfo resolveInfo : resolveInfos) {
+ if (resolveInfo == null || resolveInfo.serviceInfo == null
+ || resolveInfo.serviceInfo.applicationInfo == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Failed to retrieve a ranker: " + resolveInfo);
+ }
+ continue;
+ }
+ ComponentName componentName = new ComponentName(
+ resolveInfo.serviceInfo.applicationInfo.packageName,
+ resolveInfo.serviceInfo.name);
+ try {
+ final String perm = mPm.getServiceInfo(componentName, 0).permission;
+ if (!ResolverRankerService.BIND_PERMISSION.equals(perm)) {
+ Log.w(TAG, "ResolverRankerService " + componentName + " does not require"
+ + " permission " + ResolverRankerService.BIND_PERMISSION
+ + " - this service will not be queried for ResolverComparator."
+ + " add android:permission=\""
+ + ResolverRankerService.BIND_PERMISSION + "\""
+ + " to the <service> tag for " + componentName
+ + " in the manifest.");
+ continue;
+ }
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Could not look up service " + componentName
+ + "; component name not found");
+ continue;
}
if (DEBUG) {
- Log.d(TAG, "Weights: " + mFeatureWeights + " Bias: " + mBias);
+ Log.d(TAG, "Succeeded to retrieve a ranker: " + componentName);
}
+ intent.setComponent(componentName);
+ return intent;
+ }
+ return null;
+ }
+
+ // set a watchdog, to avoid waiting for ranking service for too long.
+ private void startWatchDog(int timeOutLimit) {
+ if (DEBUG) Log.d(TAG, "Setting watchdog timer for " + timeOutLimit + "ms");
+ if (mHandler == null) {
+ Log.d(TAG, "Error: Handler is Null; Needs to be initialized.");
+ }
+ mHandler.sendEmptyMessageDelayed(RESOLVER_RANKER_RESULT_TIMEOUT, timeOutLimit);
+ }
+
+ private class ResolverRankerServiceConnection implements ServiceConnection {
+ private final CountDownLatch mConnectSignal;
+
+ public ResolverRankerServiceConnection(CountDownLatch connectSignal) {
+ mConnectSignal = connectSignal;
}
- public void commitUpdate() {
- SharedPreferences.Editor editor = mParamSharedPref.edit();
- editor.putFloat(BIAS_PREF_KEY, mBias);
- final int size = mFeatureWeights.size();
- for (int i = 0; i < size; i++) {
- editor.putFloat(mFeatureWeights.keyAt(i), mFeatureWeights.valueAt(i));
+ public final IResolverRankerResult resolverRankerResult =
+ new IResolverRankerResult.Stub() {
+ @Override
+ public void sendResult(List<ResolverTarget> targets) throws RemoteException {
+ if (DEBUG) {
+ Log.d(TAG, "Sending Result back to Resolver: " + targets);
+ }
+ synchronized (mLock) {
+ final Message msg = Message.obtain();
+ msg.what = RESOLVER_RANKER_SERVICE_RESULT;
+ msg.obj = targets;
+ mHandler.sendMessage(msg);
+ }
}
- editor.putInt(VERSION_PREF_KEY, CURRENT_VERSION);
- editor.apply();
- }
+ };
- private SharedPreferences getParamSharedPref(Context context) {
- // The package info in the context isn't initialized in the way it is for normal apps,
- // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
- // build the path manually below using the same policy that appears in ContextImpl.
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG) {
- Log.d(TAG, "Context Package Name: " + context.getPackageName());
+ Log.d(TAG, "onServiceConnected: " + name);
}
- final File prefsFile = new File(new File(
- Environment.getDataUserCePackageDirectory(StorageManager.UUID_PRIVATE_INTERNAL,
- context.getUserId(), context.getPackageName()),
- "shared_prefs"),
- PARAM_SHARED_PREF_NAME + ".xml");
- return context.getSharedPreferences(prefsFile, Context.MODE_PRIVATE);
+ synchronized (mLock) {
+ mRanker = IResolverRankerService.Stub.asInterface(service);
+ mConnectSignal.countDown();
+ }
}
- private void initModel() {
- mFeatureWeights = new ArrayMap<>(4);
- if (mParamSharedPref == null ||
- mParamSharedPref.getInt(VERSION_PREF_KEY, 0) < CURRENT_VERSION) {
- // Initializing the app ranker to a pre-trained model. When updating the pre-trained
- // model, please increment CURRENT_VERSION, and update LEARNING_RATE and
- // REGULARIZER_PARAM.
- mBias = -1.6568f;
- mFeatureWeights.put(LAUNCH_SCORE, 2.5543f);
- mFeatureWeights.put(TIME_SPENT_SCORE, 2.8412f);
- mFeatureWeights.put(RECENCY_SCORE, 0.269f);
- mFeatureWeights.put(CHOOSER_SCORE, 4.2222f);
- } else {
- mBias = mParamSharedPref.getFloat(BIAS_PREF_KEY, 0.0f);
- mFeatureWeights.put(LAUNCH_SCORE, mParamSharedPref.getFloat(LAUNCH_SCORE, 0.0f));
- mFeatureWeights.put(
- TIME_SPENT_SCORE, mParamSharedPref.getFloat(TIME_SPENT_SCORE, 0.0f));
- mFeatureWeights.put(RECENCY_SCORE, mParamSharedPref.getFloat(RECENCY_SCORE, 0.0f));
- mFeatureWeights.put(CHOOSER_SCORE, mParamSharedPref.getFloat(CHOOSER_SCORE, 0.0f));
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DEBUG) {
+ Log.d(TAG, "onServiceDisconnected: " + name);
+ }
+ synchronized (mLock) {
+ destroy();
}
}
+
+ public void destroy() {
+ synchronized (mLock) {
+ mRanker = null;
+ }
+ }
+ }
+
+ private void reset() {
+ mTargetsDict.clear();
+ mTargets = null;
+ startWatchDog(WATCHDOG_TIMEOUT_MILLIS);
+ initRanker(mContext);
+ }
+
+ // predict select probabilities if ranking service is valid.
+ private void predictSelectProbabilities(List<ResolverTarget> targets) {
+ if (mConnection == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Has not found valid ResolverRankerService; Skip Prediction");
+ }
+ return;
+ } else {
+ try {
+ mConnectSignal.await(CONNECTION_COST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ synchronized (mLock) {
+ if (mRanker != null) {
+ mRanker.predict(targets, mConnection.resolverRankerResult);
+ return;
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Ranker has not been initialized; skip predict.");
+ }
+ }
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Error in Wait for Service Connection.");
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error in Predict: " + e);
+ }
+ }
+ mAfterCompute.afterCompute();
+ }
+
+ // adds select prob as the default values, according to a pre-trained Logistic Regression model.
+ private void addDefaultSelectProbability(ResolverTarget target) {
+ float sum = 2.5543f * target.getLaunchScore() + 2.8412f * target.getTimeSpentScore() +
+ 0.269f * target.getRecencyScore() + 4.2222f * target.getChooserScore();
+ target.setSelectProbability((float) (1.0 / (1.0 + Math.exp(1.6568f - sum))));
+ }
+
+ // sets features for each target
+ private void setFeatures(ResolverTarget target, float recencyScore, float launchScore,
+ float timeSpentScore, float chooserScore) {
+ target.setRecencyScore(recencyScore);
+ target.setLaunchScore(launchScore);
+ target.setTimeSpentScore(timeSpentScore);
+ target.setChooserScore(chooserScore);
+ }
+
+ static boolean isPersistentProcess(ResolvedComponentInfo rci) {
+ if (rci != null && rci.getCount() > 0) {
+ return (rci.getResolveInfoAt(0).activityInfo.applicationInfo.flags &
+ ApplicationInfo.FLAG_PERSISTENT) != 0;
+ }
+ return false;
}
}
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index 4071ff4..e8bebb7 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -32,8 +32,10 @@
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import java.lang.InterruptedException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.concurrent.CountDownLatch;
import java.util.List;
/**
@@ -205,14 +207,42 @@
return listToReturn;
}
+ private class ComputeCallback implements ResolverComparator.AfterCompute {
+
+ private CountDownLatch mFinishComputeSignal;
+
+ public ComputeCallback(CountDownLatch finishComputeSignal) {
+ mFinishComputeSignal = finishComputeSignal;
+ }
+
+ public void afterCompute () {
+ mFinishComputeSignal.countDown();
+ }
+ }
+
@VisibleForTesting
@WorkerThread
public void sort(List<ResolverActivity.ResolvedComponentInfo> inputList) {
+ final CountDownLatch finishComputeSignal = new CountDownLatch(1);
+ ComputeCallback callback = new ComputeCallback(finishComputeSignal);
if (mResolverComparator == null) {
- mResolverComparator = new ResolverComparator(mContext, mTargetIntent, mReferrerPackage);
+ mResolverComparator =
+ new ResolverComparator(mContext, mTargetIntent, mReferrerPackage, callback);
+ } else {
+ mResolverComparator.setCallBack(callback);
}
- mResolverComparator.compute(inputList);
- Collections.sort(inputList, mResolverComparator);
+ try {
+ long beforeRank = System.currentTimeMillis();
+ mResolverComparator.compute(inputList);
+ finishComputeSignal.await();
+ Collections.sort(inputList, mResolverComparator);
+ long afterRank = System.currentTimeMillis();
+ if (DEBUG) {
+ Log.d(TAG, "Time Cost: " + Long.toString(afterRank - beforeRank));
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "Compute & Sort was interrupted: " + e);
+ }
}
private static boolean isSameResolvedComponent(ResolveInfo a,
@@ -233,7 +263,7 @@
@VisibleForTesting
public float getScore(ResolverActivity.DisplayResolveInfo target) {
if (mResolverComparator == null) {
- mResolverComparator = new ResolverComparator(mContext, mTargetIntent, mReferrerPackage);
+ return 0.0f;
}
return mResolverComparator.getScore(target.getResolvedComponentName());
}
@@ -249,4 +279,10 @@
mResolverComparator.updateChooserCounts(packageName, userId, action);
}
}
+
+ public void destroy() {
+ if (mResolverComparator != null) {
+ mResolverComparator.destroy();
+ }
+ }
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index b2a2fec..67bce8c 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -548,10 +548,11 @@
final int dexFlags = 0;
final String compilerFilter = "speed";
final String uuid = StorageManager.UUID_PRIVATE_INTERNAL;
+ final String seInfo = null;
try {
installd.dexopt(classPathElement, Process.SYSTEM_UID, packageName,
instructionSet, dexoptNeeded, outputPath, dexFlags, compilerFilter,
- uuid, sharedLibraries);
+ uuid, sharedLibraries, seInfo);
} catch (RemoteException | ServiceSpecificException e) {
// Ignore (but log), we need this on the classpath for fallback mode.
Log.w(TAG, "Failed compiling classpath element for system server: "
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index 15b2f35..dcb2300 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -278,7 +278,8 @@
jstring ifaceNameObj,
jstring serviceNameObj) {
- using ::android::vintf::operator<<;
+ using ::android::hidl::base::V1_0::IBase;
+ using ::android::hidl::manager::V1_0::IServiceManager;
if (ifaceNameObj == NULL) {
jniThrowException(env, "java/lang/NullPointerException", NULL);
@@ -320,13 +321,20 @@
<< "/"
<< serviceName;
- ::android::vintf::Transport transport =
- ::android::hardware::getTransport(ifaceName, serviceName);
- if ( transport != ::android::vintf::Transport::EMPTY
- && transport != ::android::vintf::Transport::HWBINDER) {
+ Return<IServiceManager::Transport> transportRet =
+ manager->getTransport(ifaceNameHStr, serviceNameHStr);
+
+ if (!transportRet.isOk()) {
+ signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
+ return NULL;
+ }
+
+ IServiceManager::Transport transport = transportRet;
+
+ if ( transport != IServiceManager::Transport::EMPTY
+ && transport != IServiceManager::Transport::HWBINDER) {
LOG(ERROR) << "service " << ifaceName << " declares transport method "
- << transport << " but framework expects "
- << ::android::vintf::Transport::HWBINDER;
+ << toString(transport) << " but framework expects hwbinder.";
signalExceptionForError(env, UNKNOWN_ERROR, true /* canThrowRemoteException */);
return NULL;
}
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index a03d3c5..e8c5771 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -176,6 +176,22 @@
}
}
+void android_os_Process_setThreadGroupAndCpuset(JNIEnv* env, jobject clazz, int tid, jint grp)
+{
+ ALOGV("%s tid=%d grp=%" PRId32, __func__, tid, grp);
+ SchedPolicy sp = (SchedPolicy) grp;
+ int res = set_sched_policy(tid, sp);
+
+ if (res != NO_ERROR) {
+ signalExceptionForGroupError(env, -res, tid);
+ }
+
+ res = set_cpuset_policy(tid, sp);
+ if (res != NO_ERROR) {
+ signalExceptionForGroupError(env, -res, tid);
+ }
+}
+
void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jint grp)
{
ALOGV("%s pid=%d grp=%" PRId32, __func__, pid, grp);
@@ -1207,6 +1223,7 @@
{"getThreadPriority", "(I)I", (void*)android_os_Process_getThreadPriority},
{"getThreadScheduler", "(I)I", (void*)android_os_Process_getThreadScheduler},
{"setThreadGroup", "(II)V", (void*)android_os_Process_setThreadGroup},
+ {"setThreadGroupAndCpuset", "(II)V", (void*)android_os_Process_setThreadGroupAndCpuset},
{"setProcessGroup", "(II)V", (void*)android_os_Process_setProcessGroup},
{"getProcessGroup", "(I)I", (void*)android_os_Process_getProcessGroup},
{"getExclusiveCores", "()[I", (void*)android_os_Process_getExclusiveCores},
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index e2443bb..dc365b4 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -167,7 +167,7 @@
buffer->getHeight(),
buffer->getPixelFormat(),
buffer->getUsage(),
- (void*)buffer.get());
+ (jlong)buffer.get());
}
static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz,
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 313041e..cf6f37f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3130,6 +3130,15 @@
<permission android:name="android.permission.BIND_CHOOSER_TARGET_SERVICE"
android:protectionLevel="signature" />
+ <!-- @SystemApi Must be required by services that extend
+ {@link android.service.resolver.ResolverRankerService}, to ensure that only the system can
+ bind to them.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.BIND_RESOLVER_RANKER_SERVICE"
+ android:protectionLevel="signature" />
+
<!-- Must be required by a {@link
android.service.notification.ConditionProviderService},
to ensure that only the system can bind to it.
@@ -3641,6 +3650,13 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+ <service android:name="com.android.internal.app.LRResolverRankerService"
+ android:permission="android.permission.BIND_RESOLVER_RANKER_SERVICE"
+ android:priority="-1" >
+ <intent-filter>
+ <action android:name="android.service.resolver.ResolverRankerService" />
+ </intent-filter>
+ </service>
</application>
</manifest>
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index ac7b236..98dc4cf 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -66,4 +66,8 @@
<!-- The small screens of watch devices makes multi-window support undesireable. -->
<bool name="config_supportsMultiWindow">false</bool>
<bool name="config_supportsSplitScreenMultiWindow">false</bool>
+
+ <!-- Disable Multi-Display because of small screen space and lack of external display connection
+ options. -->
+ <bool name="config_supportsMultiDisplay">false</bool>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 221e308..3dd7ad4 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2651,6 +2651,9 @@
<!-- True if the device supports split screen as a form of multi-window. -->
<bool name="config_supportsSplitScreenMultiWindow">true</bool>
+ <!-- True if the device supports running activities on secondary displays. -->
+ <bool name="config_supportsMultiDisplay">true</bool>
+
<!-- True if the device has no home screen. That is a launcher activity
where the user can launch other applications from. -->
<bool name="config_noHomeScreen">false</bool>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 459b48f..ae28797 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4585,6 +4585,9 @@
<!-- Accessibility title for the autofill dialog used to select a list of options to autofill an activity. [CHAR LIMIT=NONE] -->
<string name="autofill_picker_accessibility_title">Autofill options</string>
+ <!-- Toast message shown when user manually request autofill but service could not figure out the data that would autofill the screen contents. [CHAR LIMIT=NONE] -->
+ <string name="autofill_error_cannot_autofill">Contents can\u2019t be autofilled</string>
+
<!-- Title for the autofill save dialog shown when the the contents of the activity can be saved
by an autofill service, but the service does not know what the activity represents [CHAR LIMIT=NONE] -->
<string name="autofill_save_title">Save to <xliff:g id="label" example="MyPass">%1$s</xliff:g>?</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 428db87..379c376 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -313,6 +313,7 @@
<java-symbol type="bool" name="config_freeformWindowManagement" />
<java-symbol type="bool" name="config_supportsMultiWindow" />
<java-symbol type="bool" name="config_supportsSplitScreenMultiWindow" />
+ <java-symbol type="bool" name="config_supportsMultiDisplay" />
<java-symbol type="bool" name="config_noHomeScreen" />
<java-symbol type="bool" name="config_guestUserEphemeral" />
<java-symbol type="bool" name="config_localDisplaysMirrorContent" />
@@ -2875,6 +2876,7 @@
<java-symbol type="id" name="autofill_save_no" />
<java-symbol type="id" name="autofill_save_yes" />
<java-symbol type="id" name="autofill_save_close" />
+ <java-symbol type="string" name="autofill_error_cannot_autofill" />
<java-symbol type="string" name="autofill" />
<java-symbol type="string" name="autofill_picker_accessibility_title " />
<java-symbol type="string" name="autofill_save_title" />
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index e03dcf3..e61d467 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -3530,7 +3530,7 @@
*
* @see RenderIntent
*/
- public RenderIntent getIntent() {
+ public RenderIntent getRenderIntent() {
return mIntent;
}
diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp
index 8b71086..959059f 100644
--- a/libs/hwui/Texture.cpp
+++ b/libs/hwui/Texture.cpp
@@ -227,10 +227,16 @@
*outType = GL_UNSIGNED_BYTE;
break;
case kRGBA_F16_SkColorType:
- // This format is always linear
- *outFormat = GL_RGBA;
- *outInternalFormat = GL_RGBA16F;
- *outType = GL_HALF_FLOAT;
+ if (caches.extensions().getMajorGlVersion() >= 3) {
+ // This format is always linear
+ *outFormat = GL_RGBA;
+ *outInternalFormat = GL_RGBA16F;
+ *outType = GL_HALF_FLOAT;
+ } else {
+ *outFormat = GL_RGBA;
+ *outInternalFormat = caches.rgbaInternalFormat(true);
+ *outType = GL_UNSIGNED_BYTE;
+ }
break;
default:
LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", colorType);
@@ -244,8 +250,17 @@
rgbaBitmap.allocPixels(SkImageInfo::MakeN32(bitmap.width(), bitmap.height(),
bitmap.info().alphaType(), hasLinearBlending ? sRGB : nullptr));
rgbaBitmap.eraseColor(0);
- SkCanvas canvas(rgbaBitmap);
- canvas.drawBitmap(bitmap, 0.0f, 0.0f, nullptr);
+
+ if (bitmap.colorType() == kRGBA_F16_SkColorType) {
+ // Drawing RGBA_F16 onto ARGB_8888 is not supported
+ bitmap.readPixels(rgbaBitmap.info()
+ .makeColorSpace(SkColorSpace::MakeSRGB()),
+ rgbaBitmap.getPixels(), rgbaBitmap.rowBytes(), 0, 0);
+ } else {
+ SkCanvas canvas(rgbaBitmap);
+ canvas.drawBitmap(bitmap, 0.0f, 0.0f, nullptr);
+ }
+
return rgbaBitmap;
}
@@ -254,7 +269,9 @@
|| info.colorType() == kIndex_8_SkColorType
|| (info.colorType() == kRGB_565_SkColorType
&& hasLinearBlending
- && info.colorSpace()->isSRGB());
+ && info.colorSpace()->isSRGB())
+ || (info.colorType() == kRGBA_F16_SkColorType
+ && Caches::getInstance().extensions().getMajorGlVersion() < 3);
}
void Texture::upload(Bitmap& bitmap) {
@@ -287,10 +304,16 @@
colorTypeToGlFormatAndType(mCaches, bitmap.colorType(),
needSRGB && hasLinearBlending, &internalFormat, &format, &type);
+ // Some devices don't support GL_RGBA16F, so we need to compare the color type
+ // and internal GL format to decide what to do with 16 bit bitmaps
+ bool rgba16fNeedsConversion = bitmap.colorType() == kRGBA_F16_SkColorType
+ && internalFormat != GL_RGBA16F;
+
mConnector.reset();
// RGBA16F is always extended sRGB, alpha masks don't have color profiles
- if (internalFormat != GL_RGBA16F && internalFormat != GL_ALPHA) {
+ // If an RGBA16F bitmap needs conversion, we know the target will be sRGB
+ if (internalFormat != GL_RGBA16F && internalFormat != GL_ALPHA && !rgba16fNeedsConversion) {
SkColorSpace* colorSpace = bitmap.info().colorSpace();
// If the bitmap is sRGB we don't need conversion
if (colorSpace != nullptr && !colorSpace->isSRGB()) {
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index eed5b24..729c7b8 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -228,7 +228,7 @@
bool hasLinearBlending = caches.extensions().hasLinearBlending();
GLint format, type, internalFormat;
uirenderer::Texture::colorTypeToGlFormatAndType(caches, skBitmap.colorType(),
- needSRGB, &internalFormat, &format, &type);
+ needSRGB && hasLinearBlending, &internalFormat, &format, &type);
PixelFormat pixelFormat = internalFormatToPixelFormat(internalFormat);
sp<GraphicBuffer> buffer = new GraphicBuffer(info.width(), info.height(), pixelFormat,
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 0b27d18..e82dd82 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -129,6 +129,23 @@
public static final String ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT =
"android.media.tv.action.PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT";
+ /**
+ * Broadcast Action: sent to the target TV input after it is first installed to notify the input
+ * to initialize its channels and programs to the system content provider.
+ *
+ * <p>Note that this intent is sent only on devices with
+ * {@link android.content.pm.PackageManager#FEATURE_LEANBACK} enabled. Besides that, in order
+ * to receive this intent, the target TV input must:
+ * <ul>
+ * <li>Declare a broadcast receiver for this intent in its
+ * <code>AndroidManifest.xml</code>.</li>
+ * <li>Declare appropriate permissions to write channel and program data in its
+ * <code>AndroidManifest.xml</code>.</li>
+ * </ul>
+ */
+ public static final String ACTION_INITIALIZE_PROGRAMS =
+ "android.media.tv.action.INITIALIZE_PROGRAMS";
+
/** The key for a bundle parameter containing a channel ID as a long integer */
public static final String EXTRA_CHANNEL_ID = "android.media.tv.extra.CHANNEL_ID";
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index ad985c7..ff310cc 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -781,8 +781,10 @@
<!-- Summary shown for color space correction preference when its value is overridden by another preference [CHAR LIMIT=35] -->
<string name="daltonizer_type_overridden">Overridden by <xliff:g id="title" example="Simulate color space">%1$s</xliff:g></string>
- <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging/discharging -->
- <string name="power_remaining_duration_only">Approx. <xliff:g id="time">%1$s</xliff:g> left</string>
+ <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery discharging -->
+ <string name="power_remaining_duration_only">About <xliff:g id="time">%1$s</xliff:g> left</string>
+ <!-- [CHAR_LIMIT=40] Label for estimated remaining duration of battery charging -->
+ <string name="power_remaining_charging_duration_only"><xliff:g id="time">%1$s</xliff:g> left until fully charged</string>
<!-- [CHAR_LIMIT=40] Short label for estimated remaining duration of battery charging/discharging -->
<string name="power_remaining_duration_only_short"><xliff:g id="time">%1$s</xliff:g> left</string>
@@ -804,41 +806,13 @@
<!-- [CHAR_LIMIT=40] Short label for battery level chart when charging with duration -->
<string name="power_charging_duration_short"><xliff:g id="level">%1$s</xliff:g> -
<xliff:g id="time">%2$s</xliff:g></string>
- <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
- <string name="power_charging_duration_ac"><xliff:g id="level">%1$s</xliff:g> -
- <xliff:g id="time">%2$s</xliff:g> until fully charged on AC</string>
- <!-- [CHAR_LIMIT=40] Short label for battery level chart when charging with duration -->
- <string name="power_charging_duration_ac_short"><xliff:g id="level">%1$s</xliff:g> -
- <xliff:g id="time">%2$s</xliff:g></string>
- <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
- <string name="power_charging_duration_usb"><xliff:g id="level">%1$s</xliff:g> -
- <xliff:g id="time">%2$s</xliff:g> until fully charged over USB</string>
- <!-- [CHAR_LIMIT=40] Short label for battery level chart when charging with duration -->
- <string name="power_charging_duration_usb_short"><xliff:g id="level">%1$s</xliff:g> -
- <xliff:g id="time">%2$s</xliff:g></string>
- <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
- <string name="power_charging_duration_wireless"><xliff:g id="level">%1$s</xliff:g> -
- <xliff:g id="time">%2$s</xliff:g> until fully charged from wireless</string>
- <!-- [CHAR_LIMIT=40] Label for battery level chart when charging with duration -->
- <string name="power_charging_duration_wireless_short"><xliff:g id="level">%1$s</xliff:g> -
- <xliff:g id="time">%2$s</xliff:g></string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_unknown">Unknown</string>
<!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging from an unknown source. -->
<string name="battery_info_status_charging">Charging</string>
- <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging on AC. -->
- <string name="battery_info_status_charging_ac">Charging on AC</string>
- <!-- [CHAR_LIMIT=20] Battery short status label when charing on AC -->
- <string name="battery_info_status_charging_ac_short">Charging</string>
- <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging over USB. -->
- <string name="battery_info_status_charging_usb">Charging over USB</string>
- <!-- [CHAR_LIMIT=20] Battery short status label when charging over USB. -->
- <string name="battery_info_status_charging_usb_short">Charging</string>
- <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging over a wireless connection. -->
- <string name="battery_info_status_charging_wireless">Charging wirelessly</string>
- <!-- [CHAR_LIMIT=20] Battery short status label when charging wirelessly. -->
- <string name="battery_info_status_charging_wireless_short">Charging</string>
+ <!-- [CHAR_LIMIT=20] Battery use screen with lower case. Battery status shown in chart label when charging from an unknown source. -->
+ <string name="battery_info_status_charging_lower">charging</string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_discharging">Not charging</string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java b/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java
index 22f8856..5b2541c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java
+++ b/packages/SettingsLib/src/com/android/settingslib/BatteryInfo.java
@@ -97,7 +97,7 @@
}
public static void getBatteryInfo(final Context context, final Callback callback) {
- BatteryInfo.getBatteryInfo(context, callback, false);
+ BatteryInfo.getBatteryInfo(context, callback, false /* shortString */);
}
public static void getBatteryInfo(final Context context, final Callback callback,
@@ -115,8 +115,8 @@
final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
Intent batteryBroadcast = context.registerReceiver(null,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
- BatteryInfo batteryInfo = BatteryInfo.getBatteryInfo(context,
- batteryBroadcast, batteryStats, elapsedRealtimeUs, shortString);
+ BatteryInfo batteryInfo = BatteryInfo.getBatteryInfo(context, batteryBroadcast,
+ batteryStats, elapsedRealtimeUs, shortString);
callback.onBatteryInfoLoaded(batteryInfo);
}
}.execute();
@@ -125,7 +125,7 @@
public static BatteryInfo getBatteryInfo(Context context, Intent batteryBroadcast,
BatteryStats stats, long elapsedRealtimeUs) {
return BatteryInfo.getBatteryInfo(context, batteryBroadcast, stats, elapsedRealtimeUs,
- false);
+ false /* shortString */);
}
public static BatteryInfo getBatteryInfo(Context context, Intent batteryBroadcast,
@@ -136,7 +136,7 @@
info.batteryPercentString = Utils.formatPercentage(info.mBatteryLevel);
info.mCharging = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
final Resources resources = context.getResources();
- info.statusLabel = Utils.getBatteryStatus(resources, batteryBroadcast, shortString);
+ info.statusLabel = Utils.getBatteryStatus(resources, batteryBroadcast);
if (!info.mCharging) {
final long drainTime = stats.computeBatteryTimeRemaining(elapsedRealtimeUs);
if (drainTime > 0) {
@@ -164,29 +164,18 @@
info.remainingTimeUs = chargeTime;
String timeString = Formatter.formatShortElapsedTime(context,
chargeTime / 1000);
- int plugType = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
- int resId;
- if (plugType == BatteryManager.BATTERY_PLUGGED_AC) {
- resId = shortString ? R.string.power_charging_duration_ac_short
- : R.string.power_charging_duration_ac;
- } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) {
- resId = shortString ? R.string.power_charging_duration_usb_short
- : R.string.power_charging_duration_usb;
- } else if (plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
- resId = shortString ? R.string.power_charging_duration_wireless_short
- : R.string.power_charging_duration_wireless;
- } else {
- resId = shortString ? R.string.power_charging_duration_short
- : R.string.power_charging_duration;
- }
- info.remainingLabel = resources.getString(R.string.power_remaining_duration_only,
+ int resId = shortString ? R.string.power_charging_duration_short
+ : R.string.power_charging_duration;
+ info.remainingLabel = resources.getString(
+ R.string.power_remaining_charging_duration_only, timeString);
+ info.mChargeLabelString = resources.getString(resId, info.batteryPercentString,
timeString);
- info.mChargeLabelString = resources.getString(
- resId, info.batteryPercentString, timeString);
} else {
+ final String chargeStatusLabel = resources.getString(
+ R.string.battery_info_status_charging_lower);
info.remainingLabel = null;
info.mChargeLabelString = resources.getString(
- R.string.power_charging, info.batteryPercentString, info.statusLabel);
+ R.string.power_charging, info.batteryPercentString, chargeStatusLabel);
}
}
return info;
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 7e7b391..78ad34a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -140,30 +140,11 @@
}
public static String getBatteryStatus(Resources res, Intent batteryChangedIntent) {
- return Utils.getBatteryStatus(res, batteryChangedIntent, false);
- }
-
- public static String getBatteryStatus(Resources res, Intent batteryChangedIntent,
- boolean shortString) {
- int plugType = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
int status = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_STATUS,
BatteryManager.BATTERY_STATUS_UNKNOWN);
String statusString;
if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
- int resId;
- if (plugType == BatteryManager.BATTERY_PLUGGED_AC) {
- resId = shortString ? R.string.battery_info_status_charging_ac_short
- : R.string.battery_info_status_charging_ac;
- } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) {
- resId = shortString ? R.string.battery_info_status_charging_usb_short
- : R.string.battery_info_status_charging_usb;
- } else if (plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
- resId = shortString ? R.string.battery_info_status_charging_wireless_short
- : R.string.battery_info_status_charging_wireless;
- } else {
- resId = R.string.battery_info_status_charging;
- }
- statusString = res.getString(resId);
+ statusString = res.getString(R.string.battery_info_status_charging);
} else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
statusString = res.getString(R.string.battery_info_status_discharging);
} else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index f31c09b..6bcf256 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -135,10 +135,18 @@
* Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the title that should be displayed for the preference.
*/
+ @Deprecated
public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
/**
* Name of the meta-data item that should be set in the AndroidManifest.xml
+ * to specify the title that should be displayed for the preference.
+ */
+ public static final String META_DATA_PREFERENCE_TITLE_RES_ID =
+ "com.android.settings.title.resid";
+
+ /**
+ * Name of the meta-data item that should be set in the AndroidManifest.xml
* to specify the summary text that should be displayed for the preference.
*/
public static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
@@ -364,7 +372,16 @@
if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
icon = metaData.getInt(META_DATA_PREFERENCE_ICON);
}
- if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
+ int resId = 0;
+ if (metaData.containsKey(META_DATA_PREFERENCE_TITLE_RES_ID)) {
+ resId = metaData.getInt(META_DATA_PREFERENCE_TITLE_RES_ID);
+ if (resId != 0) {
+ title = res.getString(resId);
+ }
+ }
+ // Fallback to legacy title extraction if we couldn't get the title through
+ // res id.
+ if ((resId == 0) && metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
if (metaData.get(META_DATA_PREFERENCE_TITLE) instanceof Integer) {
title = res.getString(metaData.getInt(META_DATA_PREFERENCE_TITLE));
} else {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
index ab7c6d2..a2becf7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTrackerFactory.java
@@ -27,7 +27,7 @@
private static WifiTracker sTestingWifiTracker;
- @Keep
+ @Keep // Keep proguard from stripping this method since it is only used in tests
public static void setTestingWifiTracker(WifiTracker tracker) {
sTestingWifiTracker = tracker;
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/BatteryInfoTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/BatteryInfoTest.java
index 1364958..962c4e7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/BatteryInfoTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/BatteryInfoTest.java
@@ -21,6 +21,7 @@
import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.SystemClock;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,14 +32,23 @@
import org.robolectric.annotation.Config;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class BatteryInfoTest {
private static final String STATUS_FULL = "Full";
- private Intent mBatteryBroadcast;
- @Mock
+ private static final String STATUS_CHARGING_NO_TIME = "Charging";
+ private static final String STATUS_CHARGING_TIME = "Charging - 2h left";
+ private static final long REMAINING_TIME_NULL = -1;
+ private static final long REMAINING_TIME = 2;
+ private Intent mDisChargingBatteryBroadcast;
+ private Intent mChargingBatteryBroadcast;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
private BatteryStats mBatteryStats;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private Context mContext;
@@ -47,21 +57,53 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- mBatteryBroadcast = new Intent();
- mBatteryBroadcast.putExtra(BatteryManager.EXTRA_PLUGGED, 0);
- mBatteryBroadcast.putExtra(BatteryManager.EXTRA_LEVEL, 0);
- mBatteryBroadcast.putExtra(BatteryManager.EXTRA_SCALE, 100);
- mBatteryBroadcast.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_FULL);
+ mDisChargingBatteryBroadcast = new Intent();
+ mDisChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_PLUGGED, 0);
+ mDisChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_LEVEL, 0);
+ mDisChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_SCALE, 100);
+ mDisChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_STATUS,
+ BatteryManager.BATTERY_STATUS_FULL);
+
+ mChargingBatteryBroadcast = new Intent();
+ mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_PLUGGED,
+ BatteryManager.BATTERY_PLUGGED_AC);
+ mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_LEVEL, 50);
+ mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_SCALE, 100);
+ mChargingBatteryBroadcast.putExtra(BatteryManager.EXTRA_STATUS,
+ BatteryManager.BATTERY_STATUS_UNKNOWN);
when(mContext.getResources().getString(R.string.battery_info_status_full))
.thenReturn(STATUS_FULL);
+ when(mContext.getResources().getString(eq(R.string.power_charging), any(),
+ any())).thenReturn(STATUS_CHARGING_NO_TIME);
+ when(mContext.getResources().getString(eq(R.string.power_charging_duration), any(),
+ any())).thenReturn(STATUS_CHARGING_TIME);
}
@Test
- public void testGetBatteryInfo_HasStatusLabel() {
- BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mBatteryBroadcast, mBatteryStats,
- SystemClock.elapsedRealtime() * 1000, true);
+ public void testGetBatteryInfo_hasStatusLabel() {
+ doReturn(REMAINING_TIME_NULL).when(mBatteryStats).computeBatteryTimeRemaining(anyLong());
+ BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mDisChargingBatteryBroadcast,
+ mBatteryStats, SystemClock.elapsedRealtime() * 1000, true);
assertThat(info.statusLabel).isEqualTo(STATUS_FULL);
}
+
+ @Test
+ public void testGetBatteryInfo_doNotShowChargingMethod_hasRemainingTime() {
+ doReturn(REMAINING_TIME).when(mBatteryStats).computeChargeTimeRemaining(anyLong());
+ BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mChargingBatteryBroadcast,
+ mBatteryStats, SystemClock.elapsedRealtime() * 1000, false);
+
+ assertThat(info.mChargeLabelString).isEqualTo(STATUS_CHARGING_TIME);
+ }
+
+ @Test
+ public void testGetBatteryInfo_doNotShowChargingMethod_noRemainingTime() {
+ doReturn(REMAINING_TIME_NULL).when(mBatteryStats).computeChargeTimeRemaining(anyLong());
+ BatteryInfo info = BatteryInfo.getBatteryInfo(mContext, mChargingBatteryBroadcast,
+ mBatteryStats, SystemClock.elapsedRealtime() * 1000, false);
+
+ assertThat(info.mChargeLabelString).isEqualTo(STATUS_CHARGING_NO_TIME);
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index 2d3c4a7..5310b7a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -231,6 +231,51 @@
}
@Test
+ public void getTilesForIntent_shouldReadMetadataTitleAsString() throws RemoteException {
+ Intent intent = new Intent();
+ Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+ List<Tile> outTiles = new ArrayList<>();
+ List<ResolveInfo> info = new ArrayList<>();
+ ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
+ URI_GET_SUMMARY, "my title", 0);
+ info.add(resolveInfo);
+
+ when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
+ .thenReturn(info);
+
+ TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
+ null /* defaultCategory */, outTiles, false /* usePriority */,
+ false /* checkCategory */);
+
+ assertThat(outTiles.size()).isEqualTo(1);
+ assertThat(outTiles.get(0).title).isEqualTo("my title");
+ }
+
+ @Test
+ public void getTilesForIntent_shouldReadMetadataTitleFromResource() throws RemoteException {
+ Intent intent = new Intent();
+ Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+ List<Tile> outTiles = new ArrayList<>();
+ List<ResolveInfo> info = new ArrayList<>();
+ ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
+ URI_GET_SUMMARY, null, 123);
+ info.add(resolveInfo);
+
+ when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
+ .thenReturn(info);
+
+ when(mResources.getString(eq(123)))
+ .thenReturn("my localized title");
+
+ TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
+ null /* defaultCategory */, outTiles, false /* usePriority */,
+ false /* checkCategory */);
+
+ assertThat(outTiles.size()).isEqualTo(1);
+ assertThat(outTiles.get(0).title).isEqualTo("my localized title");
+ }
+
+ @Test
public void getTilesForIntent_shouldNotProcessInvalidUriContentSystemApp()
throws RemoteException {
Intent intent = new Intent();
@@ -298,7 +343,13 @@
}
private static ResolveInfo newInfo(boolean systemApp, String category, String keyHint,
- String iconUri, String summaryUri) {
+ String iconUri, String summaryUri) {
+ return newInfo(systemApp, category, keyHint, iconUri, summaryUri, null, 0);
+ }
+
+ private static ResolveInfo newInfo(boolean systemApp, String category, String keyHint,
+ String iconUri, String summaryUri, String title, int titleResId) {
+
ResolveInfo info = new ResolveInfo();
info.system = systemApp;
info.activityInfo = new ActivityInfo();
@@ -317,6 +368,13 @@
if (summaryUri != null) {
info.activityInfo.metaData.putString("com.android.settings.summary_uri", summaryUri);
}
+ if (title != null) {
+ info.activityInfo.metaData.putString(TileUtils.META_DATA_PREFERENCE_TITLE, title);
+ }
+ if (titleResId != 0) {
+ info.activityInfo.metaData.putInt(
+ TileUtils.META_DATA_PREFERENCE_TITLE_RES_ID, titleResId);
+ }
info.activityInfo.applicationInfo = new ApplicationInfo();
if (systemApp) {
info.activityInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 1147f16..bf39bc4 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -183,6 +183,9 @@
<!-- to control accessibility volume -->
<uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" />
+ <!-- to access ResolverRankerServices -->
+ <uses-permission android:name="android.permission.BIND_RESOLVER_RANKER_SERVICE" />
+
<application
android:name=".SystemUIApplication"
android:persistent="true"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationViewWrapperTest.java
index 2c49f99..0dad52f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationViewWrapperTest.java
@@ -41,7 +41,7 @@
@Ignore("Broken")
@Test
public void constructor_doesntUseViewContext() throws Exception {
- new TestableNotificationViewWrapper(mContext, new View(null /* context */), null /* row */);
+ new TestableNotificationViewWrapper(mContext, null /* view */, null /* row */);
}
static class TestableNotificationViewWrapper extends NotificationViewWrapper {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 1968d2e..a57daab 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -183,9 +183,6 @@
private final SimpleStringSplitter mStringColonSplitter =
new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
- private final List<AccessibilityServiceInfo> mEnabledServicesForFeedbackTempList =
- new ArrayList<>();
-
private final Rect mTempRect = new Rect();
private final Rect mTempRect1 = new Rect();
@@ -568,7 +565,6 @@
@Override
public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType,
int userId) {
- List<AccessibilityServiceInfo> result = null;
synchronized (mLock) {
// We treat calls from a profile as if made by its parent as profiles
// share the accessibility state of the parent. The call below
@@ -577,24 +573,24 @@
.resolveCallingUserIdEnforcingPermissionsLocked(userId);
// The automation service can suppress other services.
- UserState userState = getUserStateLocked(resolvedUserId);
+ final UserState userState = getUserStateLocked(resolvedUserId);
if (userState.isUiAutomationSuppressingOtherServices()) {
return Collections.emptyList();
}
- result = mEnabledServicesForFeedbackTempList;
- result.clear();
- List<Service> services = userState.mBoundServices;
- for (int serviceCount = services.size(), i = 0; i < serviceCount; ++i) {
- Service service = services.get(i);
+ final List<Service> services = userState.mBoundServices;
+ final int serviceCount = services.size();
+ final List<AccessibilityServiceInfo> result = new ArrayList<>(serviceCount);
+ for (int i = 0; i < serviceCount; ++i) {
+ final Service service = services.get(i);
// Don't report the UIAutomation (fake service)
if (!sFakeAccessibilityServiceComponentName.equals(service.mComponentName)
&& (service.mFeedbackType & feedbackType) != 0) {
result.add(service.mAccessibilityServiceInfo);
}
}
+ return result;
}
- return result;
}
@Override
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index f80c75e..9092bdb 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -57,6 +57,7 @@
import android.view.autofill.IAutoFillManagerClient;
import android.view.autofill.IAutofillWindowPresenter;
+import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -190,6 +191,9 @@
public void onFillRequestSuccess(@Nullable FillResponse response,
@NonNull String servicePackageName) {
if (response == null) {
+ if ((mFlags & FLAG_MANUAL_REQUEST) != 0) {
+ getUiForShowing().showError(R.string.autofill_error_cannot_autofill);
+ }
// Nothing to be done, but need to notify client.
notifyUnavailableToClient();
removeSelf();
@@ -605,6 +609,13 @@
}
private ViewState startPartitionLocked(AutofillId id, AutofillValue value) {
+ // TODO(b/33197203 , b/35707731): temporary workaround until partitioning supports auth
+ if (mResponseWaitingAuth != null) {
+ final ViewState viewState =
+ new ViewState(this, id, value, this, ViewState.STATE_WAITING_RESPONSE_AUTH);
+ mViewStates.put(id, viewState);
+ return viewState;
+ }
if (DEBUG) {
Slog.d(TAG, "Starting partition for view id " + id);
}
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index f8919ee..ea5f113 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -61,6 +61,8 @@
public static final int STATE_STARTED_PARTITION = 0x20;
/** User select a dataset in this view, but service must authenticate first. */
public static final int STATE_WAITING_DATASET_AUTH = 0x40;
+ // TODO(b/33197203 , b/35707731): temporary workaround until partitioning supports auth
+ public static final int STATE_WAITING_RESPONSE_AUTH = 0x80;
public final AutofillId id;
private final Listener mListener;
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 832ff9a..257256d 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -89,6 +89,13 @@
/**
* Displays an error message to the user.
*/
+ public void showError(int resId) {
+ showError(mContext.getString(resId));
+ }
+
+ /**
+ * Displays an error message to the user.
+ */
public void showError(@Nullable CharSequence message) {
mHandler.post(() -> {
if (!hasCallback()) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7ef38b2..be5ff80 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1490,6 +1490,7 @@
boolean mSupportsSplitScreenMultiWindow;
boolean mSupportsFreeformWindowManagement;
boolean mSupportsPictureInPicture;
+ boolean mSupportsMultiDisplay;
boolean mSupportsLeanbackOnly;
IActivityController mController = null;
boolean mControllerIsAMonkey = false;
@@ -2384,7 +2385,19 @@
idleUids();
} break;
case VR_MODE_CHANGE_MSG: {
- mVrController.onVrModeChanged((ActivityRecord) msg.obj);
+ if (mVrController.onVrModeChanged((ActivityRecord) msg.obj)) {
+ synchronized (ActivityManagerService.this) {
+ if (mVrController.shouldDisableNonVrUiLocked()) {
+ // If we are in a VR mode where Picture-in-Picture mode is unsupported,
+ // then remove the pinned stack.
+ final PinnedActivityStack pinnedStack = mStackSupervisor.getStack(
+ PINNED_STACK_ID);
+ if (pinnedStack != null) {
+ mStackSupervisor.removeStackLocked(PINNED_STACK_ID);
+ }
+ }
+ }
+ }
} break;
case NOTIFY_VR_SLEEPING_MSG: {
notifyVrManagerOfSleepState(msg.arg1 != 0);
@@ -4920,7 +4933,7 @@
try {
r.forceNewConfig = true;
r.ensureActivityConfigurationLocked(0 /* globalChanges */,
- false /* preserveWindow */);
+ true /* preserveWindow */);
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -13329,6 +13342,13 @@
}
}
+ /**
+ * @return whether the system should disable UI modes incompatible with VR mode.
+ */
+ boolean shouldDisableNonVrUiLocked() {
+ return mVrController.shouldDisableNonVrUiLocked();
+ }
+
@Override
public boolean isTopOfTask(IBinder token) {
synchronized (this) {
@@ -13808,6 +13828,8 @@
com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
}
mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs;
+ mSupportsMultiDisplay = res.getBoolean(
+ com.android.internal.R.bool.config_supportsMultiDisplay);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 748aa6f..6011418 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -247,6 +247,8 @@
return runSupportsMultiwindow(pw);
case "supports-split-screen-multi-window":
return runSupportsSplitScreenMultiwindow(pw);
+ case "supports-multi-display":
+ return runSupportsMultiDisplay(pw);
case "update-appinfo":
return runUpdateApplicationInfo(pw);
case "no-home-screen":
@@ -2398,6 +2400,15 @@
return 0;
}
+ int runSupportsMultiDisplay(PrintWriter pw) throws RemoteException {
+ final Resources res = getResources(pw);
+ if (res == null) {
+ return -1;
+ }
+ pw.println(res.getBoolean(com.android.internal.R.bool.config_supportsMultiDisplay));
+ return 0;
+ }
+
int runUpdateApplicationInfo(PrintWriter pw) throws RemoteException {
int userid = UserHandle.parseUserArg(getNextArgRequired());
ArrayList<String> packages = new ArrayList<>();
@@ -2627,6 +2638,8 @@
pw.println(" Returns true if the device supports multiwindow.");
pw.println(" supports-split-screen-multi-window");
pw.println(" Returns true if the device supports split screen multiwindow.");
+ pw.println(" supports-multi-display");
+ pw.println(" Returns true if the device supports multi-display.");
pw.println(" suppress-resize-config-changes <true|false>");
pw.println(" Suppresses configuration changes due to user resizing an activity/task.");
pw.println(" set-inactive [--user <USER_ID>] <PACKAGE> true|false");
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 3a29414..6e7ad17 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1164,6 +1164,11 @@
return false;
}
+ // Check to see if we are in VR mode, and disallow PiP if so
+ if (service.shouldDisableNonVrUiLocked()) {
+ return false;
+ }
+
boolean isCurrentAppLocked = mStackSupervisor.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
boolean isKeyguardLocked = service.isKeyguardLocked();
boolean hasPinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID) != null;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 3e3fee5..30e33c8 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2782,6 +2782,15 @@
+ " reparent task=" + task + " to stackId=" + stackId);
}
+ // Ensure that we're not moving a task to a dynamic stack if device doesn't support
+ // multi-display.
+ // TODO(multi-display): Support non-dynamic stacks on secondary displays.
+ // TODO: Check ActivityView after fixing b/35349678.
+ if (StackId.isDynamicStack(stackId) && !mService.mSupportsMultiDisplay) {
+ throw new IllegalArgumentException("Device doesn't support multi-display, can not"
+ + " reparent task=" + task + " to stackId=" + stackId);
+ }
+
// Ensure that we aren't trying to move into a freeform stack without freeform
// support
if (stackId == FREEFORM_WORKSPACE_STACK_ID && !mService.mSupportsFreeformWindowManagement) {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index b408569..bb5f588 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -2178,7 +2178,8 @@
case ASSISTANT_STACK_ID:
return r.isAssistantActivity();
default:
- if (StackId.isDynamicStack(stackId)) {
+ // TODO: Check ActivityView after fixing b/35349678.
+ if (StackId.isDynamicStack(stackId) && mService.mSupportsMultiDisplay) {
return true;
}
Slog.e(TAG, "isValidLaunchStackId: Unexpected stackId=" + stackId);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index ddd918f..d83676b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -253,6 +253,17 @@
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mGlobalDisplayBrightness = pm.getDefaultScreenBrightnessSetting();
+
+ }
+
+ public void setupSchedulerPolicies() {
+ /*
+ * android.display is critical to user experience and we should
+ * make sure it is not in the default foregroup groups, add it to
+ * top-app to make sure it uses all the cores and scheduling
+ * settings for top-app when it runs.
+ */
+ Process.setThreadGroupAndCpuset(DisplayThread.get().getThreadId(), Process.THREAD_GROUP_TOP_APP);
}
@Override
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 9f7c4a2..1e2b743 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -279,13 +279,14 @@
public void dexopt(String apkPath, int uid, @Nullable String pkgName, String instructionSet,
int dexoptNeeded, @Nullable String outputPath, int dexFlags,
- String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries)
+ String compilerFilter, @Nullable String volumeUuid, @Nullable String sharedLibraries,
+ @Nullable String seInfo)
throws InstallerException {
assertValidInstructionSet(instructionSet);
if (!checkBeforeRemote()) return;
try {
mInstalld.dexopt(apkPath, uid, pkgName, instructionSet, dexoptNeeded, outputPath,
- dexFlags, compilerFilter, volumeUuid, sharedLibraries);
+ dexFlags, compilerFilter, volumeUuid, sharedLibraries, seInfo);
} catch (Exception e) {
throw InstallerException.from(e);
}
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
index 6245ffc..498181b 100644
--- a/services/core/java/com/android/server/pm/OtaDexoptService.java
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -278,7 +278,7 @@
public void dexopt(String apkPath, int uid, @Nullable String pkgName,
String instructionSet, int dexoptNeeded, @Nullable String outputPath,
int dexFlags, String compilerFilter, @Nullable String volumeUuid,
- @Nullable String sharedLibraries) throws InstallerException {
+ @Nullable String sharedLibraries, @Nullable String seInfo) throws InstallerException {
commands.add(buildCommand("dexopt",
apkPath,
uid,
@@ -289,7 +289,8 @@
dexFlags,
compilerFilter,
volumeUuid,
- sharedLibraries));
+ sharedLibraries,
+ seInfo));
}
};
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 781be34..9039647 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -204,7 +204,7 @@
long startTime = System.currentTimeMillis();
mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags,
- compilerFilter, pkg.volumeUuid, sharedLibrariesPath);
+ compilerFilter, pkg.volumeUuid, sharedLibrariesPath, pkg.applicationInfo.seInfo);
if (packageStats != null) {
long endTime = System.currentTimeMillis();
@@ -283,7 +283,7 @@
// TODO(calin): maybe add a separate call.
mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0,
/*oatDir*/ null, dexoptFlags,
- compilerFilter, info.volumeUuid, SKIP_SHARED_LIBRARY_CHECK);
+ compilerFilter, info.volumeUuid, SKIP_SHARED_LIBRARY_CHECK, info.seInfoUser);
}
return DEX_OPT_PERFORMED;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ce4d7ee..38a3089 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -8573,6 +8573,7 @@
@Override
public boolean performDexOptSecondary(String packageName, String compilerFilter,
boolean force) {
+ mDexManager.reconcileSecondaryDexFiles(packageName);
return mDexManager.dexoptSecondaryDex(packageName, compilerFilter, force);
}
diff --git a/services/core/java/com/android/server/vr/CompatibilityDisplay.java b/services/core/java/com/android/server/vr/CompatibilityDisplay.java
index 772fc26..ae1d50f 100644
--- a/services/core/java/com/android/server/vr/CompatibilityDisplay.java
+++ b/services/core/java/com/android/server/vr/CompatibilityDisplay.java
@@ -95,15 +95,20 @@
* Creates and Destroys the virtual display depending on the current state of VrMode.
*/
private void updateVirtualDisplay() {
+ boolean createVirtualDisplay = "true".equals(SystemProperties.get("vr_virtualdisplay"));
if (DEBUG) {
- Log.i(TAG, "isVrMode: " + mIsVrModeEnabled + ", override: " + mIsVrModeOverrideEnabled);
+ Log.i(TAG, "isVrMode: " + mIsVrModeEnabled + ", createVD: " + createVirtualDisplay +
+ ", override: " + mIsVrModeOverrideEnabled);
}
- if (mIsVrModeEnabled || mIsVrModeOverrideEnabled) {
+ if (mIsVrModeEnabled || (createVirtualDisplay && mIsVrModeOverrideEnabled)) {
// TODO: Consider not creating the display until ActivityManager needs one on
// which to display a 2D application.
- startVirtualDisplay();
- startImageReader();
+ // TODO: STOPSHIP Remove createVirtualDisplay conditional before launching.
+ if (createVirtualDisplay) {
+ startVirtualDisplay();
+ startImageReader();
+ }
} else {
// Stop virtual display to test exit condition
stopVirtualDisplay();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 63af2da..f74512a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -614,6 +614,10 @@
mActivityManagerService.setSystemProcess();
traceEnd();
+ // DisplayManagerService needs to setup android.display scheduling related policies
+ // since setSystemProcess() would have overridden policies due to setProcessGroup
+ mDisplayManagerService.setupSchedulerPolicies();
+
// Manages Overlay packages
traceBeginAndSlog("StartOverlayManagerService");
mSystemServiceManager.startService(new OverlayManagerService(mSystemContext, installer));
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index bb4507d..d47a67c 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -27,6 +27,10 @@
ShortcutManagerTestUtils \
truth-prebuilt
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/aidl
+
+LOCAL_SRC_FILES += aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl
+
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_PACKAGE_NAME := FrameworksServicesTests
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 205c8de..cc682c4 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -48,6 +48,8 @@
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+ <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" />
+ <uses-permission android:name="android.permission.DELETE_PACKAGES" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/services/tests/servicestests/aidl/Android.mk b/services/tests/servicestests/aidl/Android.mk
new file mode 100644
index 0000000..0c9b839
--- /dev/null
+++ b/services/tests/servicestests/aidl/Android.mk
@@ -0,0 +1,23 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := \
+ com/android/servicestests/aidl/INetworkStateObserver.aidl
+LOCAL_MODULE := servicestests-aidl
+include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/services/tests/servicestests/aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl b/services/tests/servicestests/aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl
new file mode 100644
index 0000000..ca9fc4c
--- /dev/null
+++ b/services/tests/servicestests/aidl/com/android/servicestests/aidl/INetworkStateObserver.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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.servicestests.aidl;
+
+oneway interface INetworkStateObserver {
+ /**
+ * {@param resultData} will be in the format
+ * NetinfoState|NetinfoDetailedState|RealConnectionCheck|RealConnectionCheckDetails|Netinfo.
+ * For detailed info, see
+ * servicestests/test-apps/ConnTestApp/.../ConnTestActivity#checkNetworkStatus
+ */
+ void onNetworkStateChecked(String resultData);
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/res/raw/conntestapp b/services/tests/servicestests/res/raw/conntestapp
new file mode 100644
index 0000000..6093303
--- /dev/null
+++ b/services/tests/servicestests/res/raw/conntestapp
Binary files differ
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 092c60b..6701b71 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -137,7 +137,7 @@
mHandler = new TestHandler(mHandlerThread.getLooper());
mInjector = new TestInjector();
mAms = new ActivityManagerService(mInjector);
- mAms.mWaitForNetworkTimeoutMs = 100;
+ mAms.mWaitForNetworkTimeoutMs = 2000;
when(mContext.getPackageManager()).thenReturn(mPackageManager);
}
diff --git a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
new file mode 100644
index 0000000..f971971
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.util.DebugUtils.valueToString;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.frameworks.servicestests.R;
+import com.android.servicestests.aidl.INetworkStateObserver;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import libcore.io.IoUtils;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for verifying network availability on activity start.
+ *
+ * To run the tests, use
+ *
+ * runtest -c com.android.server.net.ConnOnActivityStartTest frameworks-services
+ *
+ * or the following steps:
+ *
+ * Build: m FrameworksServicesTests
+ * Install: adb install -r \
+ * ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
+ * Run: adb shell am instrument -e class com.android.server.net.ConnOnActivityStartTest -w \
+ * com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class ConnOnActivityStartTest {
+ private static final String TAG = ConnOnActivityStartTest.class.getSimpleName();
+
+ private static final String ACTION_INSTALL_COMPLETE = "com.android.server.net.INSTALL_COMPLETE";
+
+ private static final String TEST_APP_URI =
+ "android.resource://com.android.frameworks.servicestests/raw/conntestapp";
+ private static final String TEST_PKG = "com.android.servicestests.apps.conntestapp";
+ private static final String TEST_ACTIVITY_CLASS = TEST_PKG + ".ConnTestActivity";
+
+ private static final String ACTION_FINISH_ACTIVITY = TEST_PKG + ".FINISH";
+
+ private static final String EXTRA_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
+
+ private static final int WAIT_FOR_INSTALL_TIMEOUT_MS = 2000; // 2 sec
+
+ private static final int NETWORK_CHECK_TIMEOUT_MS = 6000; // 6 sec
+
+ private static final int SCREEN_ON_DELAY_MS = 500; // 0.5 sec
+
+ private static final String NETWORK_STATUS_SEPARATOR = "\\|";
+
+ private static final int REPEAT_TEST_COUNT = 5;
+
+ private static Context mContext;
+ private static UiDevice mUiDevice;
+ private static int mTestPkgUid;
+
+ @BeforeClass
+ public static void setUpOnce() throws Exception {
+ mContext = InstrumentationRegistry.getContext();
+ mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+ installAppAndAssertInstalled();
+ mContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+ mTestPkgUid = mContext.getPackageManager().getPackageUid(TEST_PKG, 0);
+ }
+
+ @AfterClass
+ public static void tearDownOnce() {
+ mContext.getPackageManager().deletePackage(TEST_PKG,
+ new IPackageDeleteObserver.Stub() {
+ @Override
+ public void packageDeleted(String packageName, int returnCode)
+ throws RemoteException {
+ Log.e(TAG, packageName + " deleted, returnCode: " + returnCode);
+ }
+ }, 0);
+ }
+
+ @Test
+ public void testStartActivity_batterySaver() throws Exception {
+ setBatterySaverMode(true);
+ try {
+ testConnOnActivityStart("testStartActivity_batterySaver");
+ } finally {
+ setBatterySaverMode(false);
+ }
+ }
+
+ @Test
+ public void testStartActivity_dataSaver() throws Exception {
+ setDataSaverMode(true);
+ try {
+ testConnOnActivityStart("testStartActivity_dataSaver");
+ } finally {
+ setDataSaverMode(false);
+ }
+ }
+
+ @Test
+ public void testStartActivity_dozeMode() throws Exception {
+ setDozeMode(true);
+ try {
+ testConnOnActivityStart("testStartActivity_dozeMode");
+ } finally {
+ setDozeMode(false);
+ }
+ }
+
+ @Test
+ public void testStartActivity_appStandby() throws Exception {
+ try{
+ turnBatteryOff();
+ setAppIdle(true);
+ SystemClock.sleep(30000);
+ turnScreenOn();
+ startActivityAndCheckNetworkAccess();
+ } finally {
+ turnBatteryOn();
+ setAppIdle(false);
+ }
+ }
+
+ @Test
+ public void testStartActivity_backgroundRestrict() throws Exception {
+ updateRestrictBackgroundBlacklist(true);
+ try {
+ testConnOnActivityStart("testStartActivity_backgroundRestrict");
+ } finally {
+ updateRestrictBackgroundBlacklist(false);
+ }
+ }
+
+ private void testConnOnActivityStart(String testName) throws Exception {
+ for (int i = 1; i <= REPEAT_TEST_COUNT; ++i) {
+ try {
+ Log.d(TAG, testName + " Start #" + i);
+ turnScreenOn();
+ SystemClock.sleep(SCREEN_ON_DELAY_MS);
+ startActivityAndCheckNetworkAccess();
+ Log.d(TAG, testName + " end #" + i);
+ } finally {
+ finishActivity();
+ }
+ }
+ }
+
+ // TODO: Some of these methods are also used in CTS, so instead of duplicating code,
+ // create a static library which can be used by both servicestests and cts.
+ private void setBatterySaverMode(boolean enabled) throws Exception {
+ if (enabled) {
+ turnBatteryOff();
+ executeCommand("settings put global low_power 1");
+ } else {
+ executeCommand("settings put global low_power 0");
+ turnBatteryOn();
+ }
+ final String result = executeCommand("settings get global low_power");
+ assertEquals(enabled ? "1" : "0", result);
+ }
+
+ private void setDataSaverMode(boolean enabled) throws Exception {
+ executeCommand("cmd netpolicy set restrict-background " + enabled);
+ final String output = executeCommand("cmd netpolicy get restrict-background");
+ final String expectedSuffix = enabled ? "enabled" : "disabled";
+ assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'",
+ output.endsWith(expectedSuffix));
+ }
+
+ private void setDozeMode(boolean enabled) throws Exception {
+ if (enabled) {
+ turnBatteryOff();
+ turnScreenOff();
+ executeCommand("dumpsys deviceidle force-idle deep");
+ } else {
+ turnScreenOn();
+ turnBatteryOn();
+ executeCommand("dumpsys deviceidle unforce");
+ }
+ assertDelayedCommandResult("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE",
+ 5 /* maxTries */, 500 /* napTimeMs */);
+ }
+
+ private void setAppIdle(boolean enabled) throws Exception {
+ executeCommand("am set-inactive " + TEST_PKG + " " + enabled);
+ assertDelayedCommandResult("am get-inactive " + TEST_PKG, "Idle=" + enabled,
+ 10 /* maxTries */, 2000 /* napTimeMs */);
+ }
+
+ private void updateRestrictBackgroundBlacklist(boolean add) throws Exception {
+ if (add) {
+ executeCommand("cmd netpolicy add restrict-background-blacklist " + mTestPkgUid);
+ } else {
+ executeCommand("cmd netpolicy remove restrict-background-blacklist " + mTestPkgUid);
+ }
+ assertRestrictBackground("restrict-background-blacklist", mTestPkgUid, add);
+ }
+
+ private void assertRestrictBackground(String list, int uid, boolean expected) throws Exception {
+ final int maxTries = 5;
+ boolean actual = false;
+ final String expectedUid = Integer.toString(uid);
+ String uids = "";
+ for (int i = 1; i <= maxTries; i++) {
+ final String output = executeCommand("cmd netpolicy list " + list);
+ uids = output.split(":")[1];
+ for (String candidate : uids.split(" ")) {
+ actual = candidate.trim().equals(expectedUid);
+ if (expected == actual) {
+ return;
+ }
+ }
+ Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected "
+ + expected + ", got " + actual + "); sleeping 1s before polling again");
+ SystemClock.sleep(1000);
+ }
+ fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual
+ + ". Full list: " + uids);
+ }
+
+ private void turnBatteryOff() throws Exception {
+ executeCommand("cmd battery unplug");
+ }
+
+ private void turnBatteryOn() throws Exception {
+ executeCommand("cmd battery reset");
+ }
+
+ private void turnScreenOff() throws Exception {
+ executeCommand("input keyevent KEYCODE_SLEEP");
+ }
+
+ private void turnScreenOn() throws Exception {
+ executeCommand("input keyevent KEYCODE_WAKEUP");
+ executeCommand("wm dismiss-keyguard");
+ }
+
+ private String executeCommand(String cmd) throws IOException {
+ final String result = mUiDevice.executeShellCommand(cmd).trim();
+ Log.d(TAG, String.format("Result for '%s': %s", cmd, result));
+ return result;
+ }
+
+ private void assertDelayedCommandResult(String cmd, String expectedResult,
+ int maxTries, int napTimeMs) throws IOException {
+ String result = "";
+ for (int i = 1; i <= maxTries; ++i) {
+ result = executeCommand(cmd);
+ if (expectedResult.equals(result)) {
+ return;
+ }
+ Log.v(TAG, "Command '" + cmd + "' returned '" + result + " instead of '"
+ + expectedResult + "' on attempt #" + i
+ + "; sleeping " + napTimeMs + "ms before trying again");
+ SystemClock.sleep(napTimeMs);
+ }
+ fail("Command '" + cmd + "' did not return '" + expectedResult + "' after "
+ + maxTries + " attempts. Last result: '" + result + "'");
+ }
+
+ private void startActivityAndCheckNetworkAccess() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Intent launchIntent = new Intent().setComponent(
+ new ComponentName(TEST_PKG, TEST_ACTIVITY_CLASS));
+ final Bundle extras = new Bundle();
+ final String[] errors = new String[] {null};
+ extras.putBinder(EXTRA_NETWORK_STATE_OBSERVER, new INetworkStateObserver.Stub() {
+ @Override
+ public void onNetworkStateChecked(String resultData) {
+ errors[0] = checkForAvailability(resultData);
+ latch.countDown();
+ }
+ });
+ launchIntent.putExtras(extras);
+ mContext.startActivity(launchIntent);
+ if (latch.await(NETWORK_CHECK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ if (!errors[0].isEmpty()) {
+ fail("Network not available for test app " + mTestPkgUid);
+ }
+ } else {
+ fail("Timed out waiting for network availability status from test app " + mTestPkgUid);
+ }
+ }
+
+ private void finishActivity() {
+ final Intent finishIntent = new Intent(ACTION_FINISH_ACTIVITY)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mContext.sendBroadcast(finishIntent);
+ }
+
+ private String checkForAvailability(String resultData) {
+ if (resultData == null) {
+ assertNotNull("Network status from app2 is null, Uid: " + mTestPkgUid, resultData);
+ }
+ // Network status format is described on MyBroadcastReceiver.checkNetworkStatus()
+ final String[] parts = resultData.split(NETWORK_STATUS_SEPARATOR);
+ assertEquals("Wrong network status: " + resultData + ", Uid: " + mTestPkgUid,
+ 5, parts.length); // Sanity check
+ final NetworkInfo.State state = parts[0].equals("null")
+ ? null : NetworkInfo.State.valueOf(parts[0]);
+ final NetworkInfo.DetailedState detailedState = parts[1].equals("null")
+ ? null : NetworkInfo.DetailedState.valueOf(parts[1]);
+ final boolean connected = Boolean.valueOf(parts[2]);
+ final String connectionCheckDetails = parts[3];
+ final String networkInfo = parts[4];
+
+ final StringBuilder errors = new StringBuilder();
+ final NetworkInfo.State expectedState = NetworkInfo.State.CONNECTED;
+ final NetworkInfo.DetailedState expectedDetailedState = NetworkInfo.DetailedState.CONNECTED;
+
+ if (true != connected) {
+ errors.append(String.format("External site connection failed: expected %s, got %s\n",
+ true, connected));
+ }
+ if (expectedState != state || expectedDetailedState != detailedState) {
+ errors.append(String.format("Connection state mismatch: expected %s/%s, got %s/%s\n",
+ expectedState, expectedDetailedState, state, detailedState));
+ }
+
+ if (errors.length() > 0) {
+ errors.append("\tnetworkInfo: " + networkInfo + "\n");
+ errors.append("\tconnectionCheckDetails: " + connectionCheckDetails + "\n");
+ }
+ return errors.toString();
+ }
+
+ private static void installAppAndAssertInstalled() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final int[] result = {PackageInstaller.STATUS_SUCCESS};
+ final BroadcastReceiver installStatusReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String pkgName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME);
+ if (!TEST_PKG.equals(pkgName)) {
+ return;
+ }
+ result[0] = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
+ PackageInstaller.STATUS_FAILURE);
+ latch.countDown();
+ }
+ };
+ mContext.registerReceiver(installStatusReceiver, new IntentFilter(ACTION_INSTALL_COMPLETE));
+ try {
+ installApp();
+ if (latch.await(WAIT_FOR_INSTALL_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ if (result[0] != PackageInstaller.STATUS_SUCCESS) {
+ fail("Couldn't install test app, result: "
+ + valueToString(PackageInstaller.class, "STATUS_", result[0]));
+ }
+ } else {
+ fail("Timed out waiting for the test app to install");
+ }
+ } finally {
+ mContext.unregisterReceiver(installStatusReceiver);
+ }
+ }
+
+ private static void installApp() throws Exception {
+ final Uri packageUri = Uri.parse(TEST_APP_URI);
+ final InputStream in = mContext.getContentResolver().openInputStream(packageUri);
+
+ final PackageInstaller packageInstaller
+ = mContext.getPackageManager().getPackageInstaller();
+ final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ params.setAppPackageName(TEST_PKG);
+
+ final int sessionId = packageInstaller.createSession(params);
+ final PackageInstaller.Session session = packageInstaller.openSession(sessionId);
+
+ OutputStream out = null;
+ try {
+ out = session.openWrite(TAG, 0, -1);
+ final byte[] buffer = new byte[65536];
+ int c;
+ while ((c = in.read(buffer)) != -1) {
+ out.write(buffer, 0, c);
+ }
+ session.fsync(out);
+ } finally {
+ IoUtils.closeQuietly(in);
+ IoUtils.closeQuietly(out);
+ }
+ session.commit(createIntentSender(mContext, sessionId));
+ }
+
+ private static IntentSender createIntentSender(Context context, int sessionId) {
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ context, sessionId, new Intent(ACTION_INSTALL_COMPLETE), 0);
+ return pendingIntent.getIntentSender();
+ }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/ConnTestApp/Android.mk b/services/tests/servicestests/test-apps/ConnTestApp/Android.mk
new file mode 100644
index 0000000..02afe83
--- /dev/null
+++ b/services/tests/servicestests/test-apps/ConnTestApp/Android.mk
@@ -0,0 +1,30 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+
+LOCAL_STATIC_JAVA_LIBRARIES := servicestests-aidl
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := ConnTestApp
+LOCAL_CERTIFICATE := platform
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/ConnTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/ConnTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..0da3562
--- /dev/null
+++ b/services/tests/servicestests/test-apps/ConnTestApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.servicestests.apps.conntestapp">
+
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.INTERNET" />
+
+ <application>
+ <activity android:name=".ConnTestActivity"
+ android:exported="true" />
+ </application>
+
+</manifest>
\ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java b/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java
new file mode 100644
index 0000000..11ebfca
--- /dev/null
+++ b/services/tests/servicestests/test-apps/ConnTestApp/src/com/android/servicestests/apps/conntestapp/ConnTestActivity.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.servicestests.apps.conntestapp;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.servicestests.aidl.INetworkStateObserver;
+
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class ConnTestActivity extends Activity {
+ private static final String TAG = ConnTestActivity.class.getSimpleName();
+
+ private static final String TEST_PKG = ConnTestActivity.class.getPackage().getName();
+ private static final String ACTION_FINISH_ACTIVITY = TEST_PKG + ".FINISH";
+ private static final String EXTRA_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
+
+ private static final int NETWORK_TIMEOUT_MS = 5 * 1000;
+
+ private static final String NETWORK_STATUS_TEMPLATE = "%s|%s|%s|%s|%s";
+
+ private BroadcastReceiver finishCommandReceiver = null;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ notifyNetworkStateObserver();
+
+ finishCommandReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ ConnTestActivity.this.finish();
+ }
+ };
+ registerReceiver(finishCommandReceiver, new IntentFilter(ACTION_FINISH_ACTIVITY));
+ }
+
+ @Override
+ public void onStop() {
+ if (finishCommandReceiver != null) {
+ unregisterReceiver(finishCommandReceiver);
+ }
+ super.onStop();
+ }
+
+ private void notifyNetworkStateObserver() {
+ if (getIntent() == null) {
+ return;
+ }
+
+ final Bundle extras = getIntent().getExtras();
+ if (extras == null) {
+ return;
+ }
+ final INetworkStateObserver observer = INetworkStateObserver.Stub.asInterface(
+ extras.getBinder(EXTRA_NETWORK_STATE_OBSERVER));
+ if (observer != null) {
+ AsyncTask.execute(() -> {
+ try {
+ observer.onNetworkStateChecked(checkNetworkStatus(ConnTestActivity.this));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error occured while notifying the observer: " + e);
+ }
+ });
+ }
+ }
+
+ /**
+ * Checks whether the network is available and return a string which can then be send as a
+ * result data for the ordered broadcast.
+ *
+ * <p>
+ * The string has the following format:
+ *
+ * <p><pre><code>
+ * NetinfoState|NetinfoDetailedState|RealConnectionCheck|RealConnectionCheckDetails|Netinfo
+ * </code></pre>
+ *
+ * <p>Where:
+ *
+ * <ul>
+ * <li>{@code NetinfoState}: enum value of {@link NetworkInfo.State}.
+ * <li>{@code NetinfoDetailedState}: enum value of {@link NetworkInfo.DetailedState}.
+ * <li>{@code RealConnectionCheck}: boolean value of a real connection check (i.e., an attempt
+ * to access an external website.
+ * <li>{@code RealConnectionCheckDetails}: if HTTP output core or exception string of the real
+ * connection attempt
+ * <li>{@code Netinfo}: string representation of the {@link NetworkInfo}.
+ * </ul>
+ *
+ * For example, if the connection was established fine, the result would be something like:
+ * <p><pre><code>
+ * CONNECTED|CONNECTED|true|200|[type: WIFI[], state: CONNECTED/CONNECTED, reason: ...]
+ * </code></pre>
+ */
+ private String checkNetworkStatus(Context context) {
+ final ConnectivityManager cm =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ final String address = "http://example.com";
+ final NetworkInfo networkInfo = cm.getActiveNetworkInfo();
+ Log.d(TAG, "Running checkNetworkStatus() on thread "
+ + Thread.currentThread().getName() + " for UID " + getUid(context)
+ + "\n\tactiveNetworkInfo: " + networkInfo + "\n\tURL: " + address);
+ boolean checkStatus = false;
+ String checkDetails = "N/A";
+ try {
+ final URL url = new URL(address);
+ final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setReadTimeout(NETWORK_TIMEOUT_MS);
+ conn.setConnectTimeout(NETWORK_TIMEOUT_MS / 2);
+ conn.setRequestMethod("GET");
+ conn.setDoInput(true);
+ conn.connect();
+ final int response = conn.getResponseCode();
+ checkStatus = true;
+ checkDetails = "HTTP response for " + address + ": " + response;
+ } catch (Exception e) {
+ checkStatus = false;
+ checkDetails = "Exception getting " + address + ": " + e;
+ }
+ Log.d(TAG, checkDetails);
+ final String state, detailedState;
+ if (networkInfo != null) {
+ state = networkInfo.getState().name();
+ detailedState = networkInfo.getDetailedState().name();
+ } else {
+ state = detailedState = "null";
+ }
+ final String status = String.format(NETWORK_STATUS_TEMPLATE, state, detailedState,
+ Boolean.valueOf(checkStatus), checkDetails, networkInfo);
+ Log.d(TAG, "Offering " + status);
+ return status;
+ }
+
+ private int getUid(Context context) {
+ final String packageName = context.getPackageName();
+ try {
+ return context.getPackageManager().getPackageUid(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalStateException("Could not get UID for " + packageName, e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/fonts/fontchain_lint.py b/tools/fonts/fontchain_lint.py
index bb11dab..baee21c 100755
--- a/tools/fonts/fontchain_lint.py
+++ b/tools/fonts/fontchain_lint.py
@@ -413,6 +413,11 @@
global _emoji_sequences, _emoji_zwj_sequences
_emoji_properties = parse_unicode_datafile(
path.join(ucd_path, 'emoji-data.txt'), reverse=True)
+ emoji_properties_additions = parse_unicode_datafile(
+ path.join(ucd_path, 'additions', 'emoji-data.txt'), reverse=True)
+ for prop in emoji_properties_additions.keys():
+ _emoji_properties[prop].update(emoji_properties_additions[prop])
+
_chars_by_age = parse_unicode_datafile(
path.join(ucd_path, 'DerivedAge.txt'), reverse=True)
sequences = parse_standardized_variants(
@@ -420,6 +425,7 @@
_text_variation_sequences, _emoji_variation_sequences = sequences
_emoji_sequences = parse_unicode_datafile(
path.join(ucd_path, 'emoji-sequences.txt'))
+
_emoji_zwj_sequences = parse_unicode_datafile(
path.join(ucd_path, 'emoji-zwj-sequences.txt'))
_emoji_zwj_sequences.update(parse_unicode_datafile(
@@ -450,22 +456,6 @@
COMBINING_KEYCAP = 0x20E3
-# Characters that Android defaults to emoji style, different from the recommendations in UTR #51
-ANDROID_DEFAULT_EMOJI = frozenset({
- 0x2600, # BLACK SUN WITH RAYS
- 0x2601, # CLOUD
- 0x260E, # BLACK TELEPHONE
- 0x261D, # WHITE UP POINTING INDEX
- 0x263A, # WHITE SMILING FACE
- 0x2660, # BLACK SPADE SUIT
- 0x2663, # BLACK CLUB SUIT
- 0x2665, # BLACK HEART SUIT
- 0x2666, # BLACK DIAMOND SUIT
- 0x270C, # VICTORY HAND
- 0x2744, # SNOWFLAKE
- 0x2764, # HEAVY BLACK HEART
-})
-
LEGACY_ANDROID_EMOJI = {
0xFE4E5: flag_sequence('JP'),
0xFE4E6: flag_sequence('US'),
@@ -554,7 +544,6 @@
set(LEGACY_ANDROID_EMOJI.keys()))
default_emoji = (
_emoji_properties['Emoji_Presentation'] |
- ANDROID_DEFAULT_EMOJI |
all_sequences |
set(LEGACY_ANDROID_EMOJI.keys()))